diff --git a/README.md b/README.md index 204c2093..ff240068 100755 --- a/README.md +++ b/README.md @@ -24,4 +24,4 @@ Read more about the App Bundle format and Bundletool's usage at ## Releases -Latest release: [0.7.2](https://github.com/google/bundletool/releases) +Latest release: [0.8.0](https://github.com/google/bundletool/releases) diff --git a/gradle.properties b/gradle.properties index 3e19c6c8..9c674017 100755 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -release_version = 0.7.2 +release_version = 0.8.0 diff --git a/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java b/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java index 07251f31..550756d7 100755 --- a/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java +++ b/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java @@ -27,11 +27,10 @@ import com.android.tools.build.bundletool.commands.VersionCommand; import com.android.tools.build.bundletool.device.AdbServer; import com.android.tools.build.bundletool.device.DdmlibAdbServer; -import com.android.tools.build.bundletool.utils.flags.FlagParser; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.collect.ImmutableList; -import java.util.List; import java.util.Optional; /** @@ -57,8 +56,6 @@ static void main(String[] args, Runtime runtime) { runtime.exit(1); return; } - List commands = flags.getCommands(); - Optional command = flags.getMainCommand(); if (!command.isPresent()) { System.err.println("Error: You have to specify a command."); @@ -111,7 +108,7 @@ static void main(String[] args, Runtime runtime) { } break; default: - System.err.printf("Error: Unrecognized command '%s'.%n%n%n", commands.get(0)); + System.err.printf("Error: Unrecognized command '%s'.%n%n%n", command.get()); help(); runtime.exit(1); return; 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 dace8579..4692a845 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 @@ -22,25 +22,27 @@ import static com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode.UNIVERSAL; import static com.google.common.base.Preconditions.checkArgument; +import com.android.bundle.Devices.DeviceSpec; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.device.DeviceSpecParser; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.io.TempFiles; import com.android.tools.build.bundletool.model.Aapt2Command; import com.android.tools.build.bundletool.model.ApkListener; import com.android.tools.build.bundletool.model.ApkModifier; import com.android.tools.build.bundletool.model.OptimizationDimension; +import com.android.tools.build.bundletool.model.Password; import com.android.tools.build.bundletool.model.SigningConfiguration; +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.DefaultSystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.utils.SdkToolsLocator; +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; import com.android.tools.build.bundletool.splitters.DexCompressionSplitter; import com.android.tools.build.bundletool.splitters.NativeLibrariesCompressionSplitter; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.SdkToolsLocator; -import com.android.tools.build.bundletool.utils.SystemEnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.Flag.Password; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; import com.google.auto.value.AutoValue; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableSet; @@ -104,8 +106,8 @@ public enum ApkBuildMode { private static final String APK_SET_ARCHIVE_EXTENSION = "apks"; - private static final EnvironmentVariableProvider DEFAULT_PROVIDER = - new SystemEnvironmentVariableProvider(); + private static final SystemEnvironmentProvider DEFAULT_PROVIDER = + new DefaultSystemEnvironmentProvider(); public abstract Path getBundlePath(); @@ -115,7 +117,7 @@ public enum ApkBuildMode { public abstract ImmutableSet getOptimizationDimensions(); - public abstract Optional getDeviceSpecPath(); + public abstract Optional getDeviceSpec(); public abstract boolean getGenerateOnlyForConnectedDevice(); @@ -195,8 +197,8 @@ public abstract Builder setOptimizationDimensions( */ public abstract Builder setGenerateOnlyForConnectedDevice(boolean onlyForConnectedDevice); - /** Sets the path for the device spec for which the only the matching APKs will be generated. */ - public abstract Builder setDeviceSpecPath(Path deviceSpec); + /** Sets the {@link DeviceSpec} for which the only the matching APKs will be generated. */ + public abstract Builder setDeviceSpec(DeviceSpec deviceSpec); /** * Sets the device serial number. Required if more than one device including emulators is @@ -305,14 +307,14 @@ public BuildApksCommand build() { Ascii.toLowerCase(DEFAULT.name()))); } - if (command.getDeviceSpecPath().isPresent() && !command.getApkBuildMode().equals(DEFAULT)) { + if (command.getDeviceSpec().isPresent() && command.getApkBuildMode().equals(UNIVERSAL)) { throw new ValidationException( String.format( - "Optimizing for device spec only possible when running with '%s' mode flag.", - Ascii.toLowerCase(DEFAULT.name()))); + "Optimizing for device spec not possible when running with '%s' mode flag.", + Ascii.toLowerCase(UNIVERSAL.name()))); } - if (command.getGenerateOnlyForConnectedDevice() && command.getDeviceSpecPath().isPresent()) { + if (command.getGenerateOnlyForConnectedDevice() && command.getDeviceSpec().isPresent()) { throw new ValidationException( "Cannot optimize for the device spec and connected device at the same time."); } @@ -344,7 +346,7 @@ public static BuildApksCommand fromFlags(ParsedFlags flags, AdbServer adbServer) static BuildApksCommand fromFlags( ParsedFlags flags, PrintStream out, - EnvironmentVariableProvider environmentVariableProvider, + SystemEnvironmentProvider systemEnvironmentProvider, AdbServer adbServer) { BuildApksCommand.Builder buildApksCommand = BuildApksCommand.builder() @@ -363,7 +365,7 @@ static BuildApksCommand fromFlags( if (GENERATE_UNIVERSAL_APK_FLAG.getValue(flags).orElse(false)) { out.printf( "WARNING: The '%s' flag is now replaced with --mode=universal and is going to be removed " - + "in the next BundleTool version.", + + "in the next BundleTool version.%n", GENERATE_UNIVERSAL_APK_FLAG.getName()); buildApksCommand.setApkBuildMode(UNIVERSAL); } @@ -393,9 +395,19 @@ static BuildApksCommand fromFlags( } else if (!keystorePath.isPresent() && keyAlias.isPresent()) { throw new CommandExecutionException("Flag --ks is required when --ks-key-alias is set."); } else { - out.println( - "WARNING: The APKs won't be signed and thus not installable unless you also pass a " - + "keystore via the flag --ks. See the command help for more information."); + // Try to use debug keystore if present. + Optional debugConfig = + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider); + if (debugConfig.isPresent()) { + out.printf( + "INFO: The APKs will be signed with the debug keystore found at '%s'.%n", + DebugKeystoreUtils.DEBUG_KEYSTORE_CACHE.getUnchecked(systemEnvironmentProvider).get()); + buildApksCommand.setSigningConfiguration(debugConfig.get()); + } else { + out.println( + "WARNING: The APKs won't be signed and thus not installable unless you also pass a " + + "keystore via the flag --ks. See the command help for more information."); + } } boolean connectedDeviceMode = CONNECTED_DEVICE_FLAG.getValue(flags).orElse(false); @@ -405,7 +417,7 @@ static BuildApksCommand fromFlags( Optional deviceSerialName = DEVICE_ID_FLAG.getValue(flags); if (connectedDeviceMode && !deviceSerialName.isPresent()) { - deviceSerialName = environmentVariableProvider.getVariable(ANDROID_SERIAL_VARIABLE); + deviceSerialName = systemEnvironmentProvider.getVariable(ANDROID_SERIAL_VARIABLE); } deviceSerialName.ifPresent(buildApksCommand::setDeviceId); @@ -416,7 +428,7 @@ static BuildApksCommand fromFlags( Path adbPath = adbPathFromFlag.orElseGet( () -> - environmentVariableProvider + systemEnvironmentProvider .getVariable(ANDROID_HOME_VARIABLE) .flatMap(path -> new SdkToolsLocator().locateAdb(Paths.get(path))) .orElseThrow( @@ -428,7 +440,9 @@ static BuildApksCommand fromFlags( buildApksCommand.setAdbPath(adbPath).setAdbServer(adbServer); } - DEVICE_SPEC_FLAG.getValue(flags).ifPresent(buildApksCommand::setDeviceSpecPath); + DEVICE_SPEC_FLAG + .getValue(flags) + .ifPresent(path -> buildApksCommand.setDeviceSpec(DeviceSpecParser.parseDeviceSpec(path))); flags.checkNoUnknownFlags(); @@ -533,8 +547,8 @@ public static CommandHelp help() { .setOptional(true) .setDescription( "Path to the keystore that should be used to sign the generated APKs. If not " - + "set, the APKs will not be signed. If set, the flag '%s' must also be " - + "set.", + + "set, the default debug keystore will be used if it exists. If not found " + + "the APKs will not be signed. If set, the flag '%s' must also be set.", KEY_ALIAS_FLAG) .build()) .addFlag( 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 5719922e..46e2d073 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,10 +15,10 @@ */ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkDirectoryExists; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileDoesNotExist; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndExecutable; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkDirectoryExists; +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.collect.ImmutableList.toImmutableList; @@ -30,8 +30,6 @@ import com.android.bundle.Devices.DeviceSpec; import com.android.tools.build.bundletool.device.AdbServer; import com.android.tools.build.bundletool.device.DeviceAnalyzer; -import com.android.tools.build.bundletool.device.DeviceSpecParser; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.io.ApkPathManager; import com.android.tools.build.bundletool.io.ApkSerializerManager; import com.android.tools.build.bundletool.io.ApkSetBuilderFactory; @@ -46,18 +44,19 @@ import com.android.tools.build.bundletool.model.GeneratedApks; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.SigningConfiguration; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.targeting.AlternativeVariantTargetingPopulator; +import com.android.tools.build.bundletool.model.utils.SdkToolsLocator; +import com.android.tools.build.bundletool.model.utils.SplitsXmlInjector; +import com.android.tools.build.bundletool.model.utils.Versions; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.android.tools.build.bundletool.optimizations.ApkOptimizations; import com.android.tools.build.bundletool.optimizations.OptimizationsMerger; import com.android.tools.build.bundletool.splitters.ApkGenerationConfiguration; import com.android.tools.build.bundletool.splitters.ShardedApksGenerator; import com.android.tools.build.bundletool.splitters.SplitApksGenerator; -import com.android.tools.build.bundletool.targeting.AlternativeVariantTargetingPopulator; -import com.android.tools.build.bundletool.utils.SdkToolsLocator; -import com.android.tools.build.bundletool.utils.SplitsXmlInjector; -import com.android.tools.build.bundletool.utils.Versions; import com.android.tools.build.bundletool.validation.AppBundleValidator; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.UncheckedIOException; @@ -82,11 +81,9 @@ public Path execute(Path tempDir) { command.getAapt2Command().orElseGet(() -> extractAapt2FromJar(tempDir)); // Fail fast with ADB before generating any APKs. - Optional deviceSpec = Optional.empty(); + Optional deviceSpec = command.getDeviceSpec(); if (command.getGenerateOnlyForConnectedDevice()) { - deviceSpec = Optional.of(getDeviceSpec()); - } else if (command.getDeviceSpecPath().isPresent()) { - deviceSpec = Optional.of(DeviceSpecParser.parseDeviceSpec(command.getDeviceSpecPath().get())); + deviceSpec = Optional.of(getDeviceSpecFromConnectedDevice()); } try (ZipFile bundleZip = new ZipFile(command.getBundlePath().toFile())) { @@ -106,13 +103,13 @@ public Path execute(Path tempDir) { BundleConfig bundleConfig = appBundle.getBundleConfig(); Version bundleVersion = BundleToolVersion.getVersionFromBundleConfig(bundleConfig); - ImmutableList allModules = - ImmutableList.copyOf(appBundle.getModules().values()); + ImmutableList allFeatureModules = + ImmutableList.copyOf(appBundle.getFeatureModules().values()); GeneratedApks.Builder generatedApksBuilder = GeneratedApks.builder(); + boolean isApexBundle = appBundle.getBaseModule().getApexConfig().isPresent(); switch (command.getApkBuildMode()) { case DEFAULT: - boolean isApexBundle = appBundle.getBaseModule().getApexConfig().isPresent(); boolean generateSplitApks = !targetsOnlyPreL(appBundle) && !isApexBundle; boolean generateStandaloneApks = targetsPreL(appBundle) || isApexBundle; @@ -134,32 +131,36 @@ public Path execute(Path tempDir) { } if (generateSplitApks) { - ApkGenerationConfiguration.Builder apkGenerationConfiguration = - getApkGenerationConfigurationBuilder(appBundle, bundleConfig, bundleVersion); + ApkGenerationConfiguration commonApkGenerationConfiguration = + getCommonSplitApkGenerationConfiguration(appBundle, bundleConfig, bundleVersion); generatedApksBuilder.setSplitApks( new SplitApksGenerator( - allModules, + allFeatureModules, bundleVersion, - apkGenerationConfiguration.setForInstantAppVariants(false).build()) + commonApkGenerationConfiguration, + appBundle.getMasterResourcesPredicate()) .generateSplits()); - // Generate instant splits for any instant compatible modules. + // Generate instant splits for any instant compatible feature modules. ImmutableList instantModules = - allModules.stream() + allFeatureModules.stream() .filter(BundleModule::isInstantModule) .collect(toImmutableList()); - + ApkGenerationConfiguration instantApkGenerationCofiguration = + commonApkGenerationConfiguration + .toBuilder() + .setForInstantAppVariants(true) + // We can't enable this splitter for instant APKs, as currently they + // only support one variant. + .setEnableDexCompressionSplitter(false) + .build(); generatedApksBuilder.setInstantApks( new SplitApksGenerator( instantModules, bundleVersion, - apkGenerationConfiguration - .setForInstantAppVariants(true) - // We can't enable this splitter for instant APKs, as currently they - // only support one variant. - .setEnableDexCompressionSplitter(false) - .build()) + instantApkGenerationCofiguration, + appBundle.getMasterResourcesPredicate()) .generateSplits()); } if (generateStandaloneApks) { @@ -171,19 +172,22 @@ public Path execute(Path tempDir) { /* generate64BitShards= */ !appBundle.has32BitRenderscriptCode()); generatedApksBuilder.setStandaloneApks( isApexBundle - ? shardedApksGenerator.generateApexSplits(modulesToFuse(allModules)) + ? shardedApksGenerator.generateApexSplits(modulesToFuse(allFeatureModules)) : shardedApksGenerator.generateSplits( - modulesToFuse(allModules), + modulesToFuse(allFeatureModules), appBundle.getBundleMetadata(), getApkOptimizations(bundleConfig))); } break; case UNIVERSAL: + if (isApexBundle) { + throw new CommandExecutionException("APEX bundles do not support universal apks."); + } // Note: Universal APK is a special type of standalone, with no optimization dimensions. generatedApksBuilder.setStandaloneApks( new ShardedApksGenerator(tempDir, bundleVersion) .generateSplits( - modulesToFuse(allModules), + modulesToFuse(allFeatureModules), appBundle.getBundleMetadata(), ApkOptimizations.getOptimizationsForUniversalApk())); break; @@ -196,10 +200,11 @@ public Path execute(Path tempDir) { bundleVersion, SplitType.SYSTEM, /* generate64BitShards= */ !appBundle.has32BitRenderscriptCode()) - .generateSplits( - modulesToFuse(allModules), + .generateSystemSplits( + modulesToFuse(allFeatureModules), appBundle.getBundleMetadata(), - getApkOptimizations(bundleConfig))); + getApkOptimizations(bundleConfig), + deviceSpec)); break; } @@ -215,6 +220,7 @@ public Path execute(Path tempDir) { createApkSetBuilder( aapt2Command, command.getSigningConfiguration(), + bundleVersion, bundleConfig.getCompression(), tempDir); @@ -230,7 +236,8 @@ public Path execute(Path tempDir) { ImmutableList allVariantsWithTargeting; if (deviceSpec.isPresent()) { allVariantsWithTargeting = - apkSerializerManager.serializeApksForDevice(generatedApks, deviceSpec.get()); + apkSerializerManager.serializeApksForDevice( + generatedApks, deviceSpec.get(), command.getApkBuildMode()); } else { allVariantsWithTargeting = apkSerializerManager.serializeApks(generatedApks, command.getApkBuildMode()); @@ -265,7 +272,7 @@ private void printWarning(String message) { command.getOutputPrintStream().ifPresent(out -> out.println("WARNING: " + message)); } - private DeviceSpec getDeviceSpec() { + private DeviceSpec getDeviceSpecFromConnectedDevice() { AdbServer adbServer = command.getAdbServer().get(); adbServer.init(command.getAdbPath().get()); @@ -275,14 +282,16 @@ private DeviceSpec getDeviceSpec() { private ApkSetBuilder createApkSetBuilder( Aapt2Command aapt2Command, Optional signingConfiguration, + Version bundleVersion, Compression compression, Path tempDir) { ApkPathManager apkPathmanager = new ApkPathManager(); SplitApkSerializer splitApkSerializer = - new SplitApkSerializer(apkPathmanager, aapt2Command, signingConfiguration, compression); + new SplitApkSerializer( + apkPathmanager, aapt2Command, signingConfiguration, bundleVersion, compression); StandaloneApkSerializer standaloneApkSerializer = new StandaloneApkSerializer( - apkPathmanager, aapt2Command, signingConfiguration, compression); + apkPathmanager, aapt2Command, signingConfiguration, bundleVersion, compression); if (!command.getCreateApkSetArchive()) { return ApkSetBuilderFactory.createApkSetWithoutArchiveBuilder( @@ -292,7 +301,7 @@ private ApkSetBuilder createApkSetBuilder( splitApkSerializer, standaloneApkSerializer, tempDir); } - private ApkGenerationConfiguration.Builder getApkGenerationConfigurationBuilder( + private ApkGenerationConfiguration getCommonSplitApkGenerationConfiguration( AppBundle appBundle, BundleConfig bundleConfig, Version bundleToolVersion) { ApkOptimizations apkOptimizations = getApkOptimizations(bundleConfig); @@ -307,7 +316,7 @@ private ApkGenerationConfiguration.Builder getApkGenerationConfigurationBuilder( if (appBundle.has32BitRenderscriptCode()) { apkGenerationConfiguration.setInclude64BitLibs(false); } - return apkGenerationConfiguration; + return apkGenerationConfiguration.build(); } private ImmutableList modulesToFuse(ImmutableList modules) { diff --git a/src/main/java/com/android/tools/build/bundletool/commands/BuildBundleCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/BuildBundleCommand.java index 5a0e3d8a..66f591ea 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/BuildBundleCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/BuildBundleCommand.java @@ -16,11 +16,11 @@ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.model.AppBundle.BUNDLE_CONFIG_FILE_NAME; -import static com.android.tools.build.bundletool.model.AppBundle.METADATA_DIRECTORY; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileDoesNotExist; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileDoesNotExist; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileHasExtension; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.io.MoreFiles.getNameWithoutExtension; import com.android.bundle.Config.BundleConfig; import com.android.bundle.Config.Bundletool; @@ -29,21 +29,20 @@ import com.android.bundle.Files.NativeLibraries; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.io.ZipBuilder; -import com.android.tools.build.bundletool.io.ZipBuilder.EntryOption; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; +import com.android.tools.build.bundletool.io.AppBundleSerializer; +import com.android.tools.build.bundletool.model.AppBundle; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; -import com.android.tools.build.bundletool.model.InputStreamSupplier; +import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.targeting.TargetingGenerator; -import com.android.tools.build.bundletool.utils.ZipUtils; -import com.android.tools.build.bundletool.utils.files.BufferedIo; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +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.targeting.TargetingGenerator; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.android.tools.build.bundletool.validation.BundleModulesValidator; -import com.android.tools.build.bundletool.version.BundleToolVersion; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -55,7 +54,6 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; import java.util.Optional; import java.util.zip.ZipException; import java.util.zip.ZipFile; @@ -126,7 +124,7 @@ void addMetadataFileInternal(ZipPath metadataPath, Path file) { .withMessage("Metadata file '%s' does not exist.", file) .build(); } - bundleMetadataBuilder().addFile(metadataPath, () -> BufferedIo.inputStream(file)); + bundleMetadataBuilder().addFile(metadataPath, BufferedIo.inputStreamSupplier(file)); } /** @@ -175,58 +173,48 @@ public static BuildBundleCommand fromFlags(ParsedFlags flags) { public void execute() { validateInput(); - ZipBuilder bundleBuilder = new ZipBuilder(); - try (Closer closer = Closer.create()) { - EntryOption[] compression = - getUncompressedBundle() - ? new EntryOption[] {EntryOption.UNCOMPRESSED} - : new EntryOption[0]; - - // Merge in all the modules, each module into its own sub-directory. - for (Path module : getModulesPaths()) { + ImmutableList.Builder moduleZipFilesBuilder = ImmutableList.builder(); + for (Path modulePath : getModulesPaths()) { try { - ZipFile moduleZipFile = closer.register(new ZipFile(module.toFile())); - ZipPath moduleDir = ZipPath.create(getNameWithoutExtension(module)); - - bundleBuilder.copyAllContentsFromZip(moduleDir, moduleZipFile, compression); - - Optional assetsTargeting = generateAssetsTargeting(moduleZipFile); - assetsTargeting.ifPresent( - targeting -> - bundleBuilder.addFileWithProtoContent( - moduleDir.resolve("assets.pb"), targeting, compression)); - - Optional nativeLibrariesTargeting = - generateNativeLibrariesTargeting(moduleZipFile); - nativeLibrariesTargeting.ifPresent( - targeting -> - bundleBuilder.addFileWithProtoContent( - moduleDir.resolve("native.pb"), targeting, compression)); - - Optional apexImagesTargeting = generateApexImagesTargeting(moduleZipFile); - apexImagesTargeting.ifPresent( - targeting -> - bundleBuilder.addFileWithProtoContent( - moduleDir.resolve("apex.pb"), targeting, compression)); + moduleZipFilesBuilder.add(closer.register(new ZipFile(modulePath.toFile()))); } catch (ZipException e) { throw CommandExecutionException.builder() .withCause(e) - .withMessage("File '%s' does not seem to be a valid ZIP file.", module) + .withMessage("File '%s' does not seem to be a valid ZIP file.", modulePath) .build(); } catch (IOException e) { throw CommandExecutionException.builder() .withCause(e) - .withMessage("Unable to read file '%s'.", module) - .build(); - } catch (CommandExecutionException e) { - // Re-throw with additional context. - throw CommandExecutionException.builder() - .withCause(e) - .withMessage("Error processing module file '%s'.", module) + .withMessage("Unable to read file '%s'.", modulePath) .build(); } } + ImmutableList moduleZipFiles = moduleZipFilesBuilder.build(); + + ImmutableList modules = new BundleModulesValidator().validate(moduleZipFiles); + checkState( + moduleZipFiles.size() == modules.size(), + "Incorrect number of modules parsed (%s != %s).", + moduleZipFiles.size(), + modules.size()); + + ImmutableList.Builder modulesWithTargeting = ImmutableList.builder(); + for (BundleModule module : modules) { + BundleModule.Builder moduleWithTargeting = module.toBuilder(); + + Optional assetsTargeting = generateAssetsTargeting(module); + assetsTargeting.ifPresent(moduleWithTargeting::setAssetsConfig); + + Optional nativeLibrariesTargeting = + generateNativeLibrariesTargeting(module); + nativeLibrariesTargeting.ifPresent(moduleWithTargeting::setNativeConfig); + + Optional apexImagesTargeting = generateApexImagesTargeting(module); + apexImagesTargeting.ifPresent(moduleWithTargeting::setApexConfig); + + modulesWithTargeting.add(moduleWithTargeting.build()); + } // Read the Bundle Config file if provided by the developer. BundleConfig bundleConfig = @@ -237,41 +225,32 @@ public void execute() { Bundletool.newBuilder() .setVersion(BundleToolVersion.getCurrentVersion().toString())) .build(); - bundleBuilder.addFileWithContent( - ZipPath.create(BUNDLE_CONFIG_FILE_NAME), bundleConfig.toByteArray(), compression); - - // Add metadata files. - for (Map.Entry metadataEntry : - getBundleMetadata().getFileDataMap().entrySet()) { - bundleBuilder.addFile( - METADATA_DIRECTORY.resolve(metadataEntry.getKey()), - metadataEntry.getValue(), - compression); - } - try { - bundleBuilder.writeTo(getOutputPath()); - } catch (IOException e) { - throw CommandExecutionException.builder() - .withCause(e) - .withMessage("Unable to write file to location '%s'.") - .build(); - } + AppBundle appBundle = + AppBundle.buildFromModules( + modulesWithTargeting.build(), bundleConfig, getBundleMetadata()); + + new AppBundleSerializer(getUncompressedBundle()).writeToDisk(appBundle, getOutputPath()); } catch (IOException e) { throw new UncheckedIOException(e); } } private void validateInput() { + getModulesPaths() + .forEach( + path -> { + checkFileHasExtension("File", path, ".zip"); + checkFileExistsAndReadable(path); + }); checkFileDoesNotExist(getOutputPath()); - - new BundleModulesValidator().validate(getModulesPaths()); } - private Optional generateAssetsTargeting(ZipFile module) { + private static Optional generateAssetsTargeting(BundleModule module) { ImmutableList assetDirectories = - ZipUtils.allFileEntriesPaths(module) - .filter(path -> path.startsWith(BundleModule.ASSETS_DIRECTORY)) + module + .findEntriesUnderPath(BundleModule.ASSETS_DIRECTORY) + .map(ModuleEntry::getPath) .filter(path -> path.getNameCount() > 1) .map(ZipPath::getParent) .distinct() @@ -284,12 +263,13 @@ private Optional generateAssetsTargeting(ZipFile module) { return Optional.of(new TargetingGenerator().generateTargetingForAssets(assetDirectories)); } - private Optional generateNativeLibrariesTargeting(ZipFile module) { + private static Optional generateNativeLibrariesTargeting(BundleModule module) { // Validation ensures that files under "lib/" conform to pattern "lib//file.so". // We extract the distinct "lib/" directories. ImmutableList libAbiDirs = - ZipUtils.allFileEntriesPaths(module) - .filter(path -> path.startsWith(BundleModule.LIB_DIRECTORY)) + module + .findEntriesUnderPath(BundleModule.LIB_DIRECTORY) + .map(ModuleEntry::getPath) .filter(path -> path.getNameCount() > 2) .map(path -> path.subpath(0, 2)) .map(ZipPath::toString) @@ -303,12 +283,13 @@ private Optional generateNativeLibrariesTargeting(ZipFile modul return Optional.of(new TargetingGenerator().generateTargetingForNativeLibraries(libAbiDirs)); } - private Optional generateApexImagesTargeting(ZipFile module) { + private static Optional generateApexImagesTargeting(BundleModule module) { // Validation ensures that files under "apex/" conform to the pattern // "apex/.....img". ImmutableList apexImageFiles = - ZipUtils.allFileEntriesPaths(module) - .filter(path -> path.startsWith(BundleModule.APEX_DIRECTORY)) + module + .findEntriesUnderPath(BundleModule.APEX_DIRECTORY) + .map(ModuleEntry::getPath) .collect(toImmutableList()); if (apexImageFiles.isEmpty()) { diff --git a/src/main/java/com/android/tools/build/bundletool/commands/CommandHelp.java b/src/main/java/com/android/tools/build/bundletool/commands/CommandHelp.java index 5a90bbad..965846db 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/CommandHelp.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/CommandHelp.java @@ -23,17 +23,22 @@ import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Iterables; import com.google.errorprone.annotations.FormatMethod; import com.google.errorprone.annotations.FormatString; +import com.google.errorprone.annotations.Immutable; import java.io.PrintStream; import java.text.BreakIterator; import java.util.Comparator; import java.util.Locale; import java.util.Optional; +import java.util.StringJoiner; import javax.annotation.CheckReturnValue; /** Helper to print command helps in the console. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class CommandHelp { private static final String LINE_SEPARATOR = System.lineSeparator(); @@ -46,12 +51,27 @@ public abstract class CommandHelp { abstract String getCommandName(); + abstract ImmutableList getSubCommandNames(); + + private String getSubCommandNamesAsString() { + switch (getSubCommandNames().size()) { + case 0: + return ""; + case 1: + return Iterables.getOnlyElement(getSubCommandNames()); + default: + StringJoiner joiner = new StringJoiner("|", "<", ">"); + getSubCommandNames().forEach(joiner::add); + return joiner.toString(); + } + } + abstract CommandDescription getCommandDescription(); abstract ImmutableSortedSet getFlags(); static Builder builder() { - return new AutoValue_CommandHelp.Builder(); + return new AutoValue_CommandHelp.Builder().setSubCommandNames(ImmutableList.of()); } @AutoValue.Builder @@ -61,6 +81,8 @@ abstract static class Builder { public abstract Builder setCommandName(String commandName); + public abstract Builder setSubCommandNames(ImmutableList subCommandNames); + public abstract Builder setCommandDescription(CommandDescription commandDescription); abstract Builder setFlags(ImmutableSortedSet flags); @@ -111,7 +133,12 @@ public void printDetails(PrintStream output) { } output.println("Synopsis:"); - output.println(wrap("bundletool " + getCommandName(), MAX_WIDTH, INDENT_SIZE, INDENT_SIZE)); + output.println( + wrap( + String.format("bundletool %s %s", getCommandName(), getSubCommandNamesAsString()), + MAX_WIDTH, + INDENT_SIZE, + INDENT_SIZE)); for (FlagDescription flag : getFlags()) { output.println( wrap( @@ -135,7 +162,9 @@ public void printDetails(PrintStream output) { } /** Full description of a command for the command-line help. */ + @Immutable @AutoValue + @AutoValue.CopyAnnotations abstract static class CommandDescription { abstract String getShortDescription(); @@ -170,7 +199,9 @@ Builder addAdditionalParagraph(String additionalParaghaph) { } /** Full description of a flag for the command-line help. */ + @Immutable @AutoValue + @AutoValue.CopyAnnotations abstract static class FlagDescription implements Comparable { abstract String getFlagName(); diff --git a/src/main/java/com/android/tools/build/bundletool/commands/DebugKeystoreUtils.java b/src/main/java/com/android/tools/build/bundletool/commands/DebugKeystoreUtils.java new file mode 100755 index 00000000..e2cc230b --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/commands/DebugKeystoreUtils.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 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.commands; + +import com.android.tools.build.bundletool.model.Password; +import com.android.tools.build.bundletool.model.SigningConfiguration; +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore.PasswordProtection; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +/** Utility methods related to debug keystore. */ +public final class DebugKeystoreUtils { + + private static final String ANDROID_SDK_HOME = "ANDROID_SDK_HOME"; + private static final String HOME = "HOME"; + private static final String USER_HOME = "user.home"; + + private static final String DEBUG_KEY_ALIAS = "AndroidDebugKey"; + private static final String ANDROID_DOT_DIR = ".android"; + private static final String DEBUG_KEYSTORE_FILENAME = "debug.keystore"; + + public static final Password DEBUG_KEY_PASSWORD = + new Password(() -> new PasswordProtection("android".toCharArray())); + + public static final LoadingCache> DEBUG_KEYSTORE_CACHE = + CacheBuilder.newBuilder().build(CacheLoader.from(DebugKeystoreUtils::getDebugKeystorePath)); + + public static Optional getDebugSigningConfiguration( + SystemEnvironmentProvider provider) { + try { + return DEBUG_KEYSTORE_CACHE + .get(provider) + .map( + keystorePath -> + SigningConfiguration.extractFromKeystore( + keystorePath, + DEBUG_KEY_ALIAS, + Optional.of(DEBUG_KEY_PASSWORD), + Optional.of(DEBUG_KEY_PASSWORD))); + } catch (ExecutionException e) { + return Optional.empty(); + } + } + + /** + * Returns the path to the debug keystore if it exists. + * + *

The path resolution logic is based on AOSP resolution rules. + */ + private static Optional getDebugKeystorePath(SystemEnvironmentProvider envProvider) { + return Stream.of( + envProvider.getVariable(ANDROID_SDK_HOME), + envProvider.getProperty(USER_HOME), + envProvider.getVariable(HOME)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(Paths::get) + .filter(Files::isDirectory) + .map(path -> path.resolve(ANDROID_DOT_DIR).resolve(DEBUG_KEYSTORE_FILENAME)) + .filter(Files::exists) + .filter(Files::isReadable) + .findFirst(); + } + + private DebugKeystoreUtils() {} +} diff --git a/src/main/java/com/android/tools/build/bundletool/commands/DumpCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/DumpCommand.java index 11eaa353..99e8a1fc 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/DumpCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/DumpCommand.java @@ -15,24 +15,23 @@ */ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.function.Function.identity; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ResourceTableEntry; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import java.io.PrintStream; import java.nio.file.Path; import java.util.Arrays; import java.util.Optional; -import java.util.Set; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -243,15 +242,13 @@ public static DumpTarget fromString(String subCommand) { } public static CommandHelp help() { - Set dumpTargets = DumpTarget.SUBCOMMAND_TO_TARGET.keySet(); - return CommandHelp.builder() .setCommandName(COMMAND_NAME) + .setSubCommandNames(DumpTarget.SUBCOMMAND_TO_TARGET.keySet().asList()) .setCommandDescription( CommandDescription.builder() .setShortDescription( "Prints files or extract values from the bundle in a human-readable form.") - .addAdditionalParagraph("Subcommands available: " + dumpTargets) .addAdditionalParagraph("Examples:") .addAdditionalParagraph( String.format( 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 d877732f..1b7285c3 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 @@ -15,22 +15,22 @@ */ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.utils.CollectorUtils.groupingBySortedKeys; +import static com.android.tools.build.bundletool.model.utils.CollectorUtils.groupingBySortedKeys; import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.aapt.ConfigurationOuterClass.Configuration; import com.android.aapt.Resources.ConfigValue; import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModule.SpecialModuleEntry; 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.utils.ResourcesUtils; -import com.android.tools.build.bundletool.utils.ZipUtils; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoPrintUtils; +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; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoPrintUtils; import com.android.tools.build.bundletool.xml.XPathResolver; import com.android.tools.build.bundletool.xml.XPathResolver.XPathResult; import com.android.tools.build.bundletool.xml.XmlNamespaceContext; @@ -66,7 +66,8 @@ final class DumpManager { void printManifest(BundleModuleName moduleName, Optional xPathExpression) { // Extract the manifest from the bundle. - ZipPath manifestPath = ZipPath.create(moduleName.getName()).resolve(BundleModule.MANIFEST_PATH); + ZipPath manifestPath = + ZipPath.create(moduleName.getName()).resolve(SpecialModuleEntry.ANDROID_MANIFEST.getPath()); XmlProtoNode manifestProto = new XmlProtoNode(extractAndParse(bundlePath, manifestPath, XmlNode::parseFrom)); @@ -98,7 +99,7 @@ void printResources(Predicate resourcePredicate, boolean pri try (ZipFile zipFile = new ZipFile(bundlePath.toFile())) { resourceTables = ZipUtils.allFileEntriesPaths(zipFile) - .filter(path -> path.endsWith(BundleModule.RESOURCES_PROTO_PATH)) + .filter(path -> path.endsWith(SpecialModuleEntry.RESOURCE_TABLE.getPath())) .map(path -> extractAndParse(zipFile, path, ResourceTable::parseFrom)) .collect(toImmutableList()); } catch (IOException e) { @@ -134,9 +135,9 @@ private void printEntry(ResourceTableEntry entry, boolean printValues) { } if (printValues) { printStream.printf( - " - %s [%s]", - XmlProtoPrintUtils.getValueAsString(configValue.getValue()), - XmlProtoPrintUtils.getValueTypeAsString(configValue.getValue())); + " - [%s] %s", + XmlProtoPrintUtils.getValueTypeAsString(configValue.getValue()), + XmlProtoPrintUtils.getValueAsString(configValue.getValue())); } printStream.println(); } 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 317620d3..6ac63270 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 @@ -16,23 +16,26 @@ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkDirectoryExists; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkDirectoryExists; +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.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import com.android.bundle.Commands.BuildApksResult; import com.android.bundle.Devices.DeviceSpec; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; import com.android.tools.build.bundletool.device.ApkMatcher; import com.android.tools.build.bundletool.device.DeviceSpecParser; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.ResultUtils; -import com.android.tools.build.bundletool.utils.files.BufferedIo; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.FileNames; +import com.android.tools.build.bundletool.model.utils.ResultUtils; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; @@ -54,6 +57,7 @@ public abstract class ExtractApksCommand { public static final String COMMAND_NAME = "extract-apks"; + static final String ALL_MODULES_SHORTCUT = "_ALL_"; private static final Flag APKS_ARCHIVE_FILE_FLAG = Flag.path("apks"); private static final Flag DEVICE_SPEC_FLAG = Flag.path("device-spec"); @@ -136,10 +140,20 @@ public ImmutableList execute() { ImmutableList execute(PrintStream output) { validateInput(); - ApkMatcher apkMatcher = - new ApkMatcher(getDeviceSpec(), /* requestedModuleNames= */ getModules(), getInstant()); - ImmutableList matchedApks = - apkMatcher.getMatchingApks(ResultUtils.readTableOfContents(getApksArchivePath())); + BuildApksResult toc = ResultUtils.readTableOfContents(getApksArchivePath()); + Optional> requestedModuleNames = + getModules() + .map( + modules -> + modules.contains(ALL_MODULES_SHORTCUT) + ? toc.getVariantList().stream() + .flatMap(variant -> variant.getApkSetList().stream()) + .map(apkSet -> apkSet.getModuleMetadata().getName()) + .collect(toImmutableSet()) + : modules); + + ApkMatcher apkMatcher = new ApkMatcher(getDeviceSpec(), requestedModuleNames, getInstant()); + ImmutableList matchedApks = apkMatcher.getMatchingApks(toc); if (Files.isDirectory(getApksArchivePath())) { @@ -161,6 +175,7 @@ private void validateInput() { !getOutputDirectory().isPresent(), "Output directory should not be set when APKs are inside directory."); checkDirectoryExists(getApksArchivePath()); + checkFileExistsAndReadable(getApksArchivePath().resolve(FileNames.TABLE_OF_CONTENTS_FILE)); } else { checkFileExistsAndReadable(getApksArchivePath()); } @@ -193,7 +208,7 @@ private ImmutableList extractMatchedApksFromApksArchive( e); } System.err.printf( - "The APKs have been extracted in the directory: %s\n", outputDirectoryPath.toString()); + "The APKs have been extracted in the directory: %s%n", outputDirectoryPath.toString()); return builder.build(); } @@ -245,9 +260,11 @@ public static CommandHelp help() { .setExampleValue("base,module1,module2") .setOptional(true) .setDescription( - "List of modules to be extracted (defaults to all of them). Note that the " - + "dependent modules will also be extracted. Ignored if the device " - + "receives a standalone APK.") + "List of modules to be extracted, or \"%s\" for all modules. Defaults to " + + "modules installed during the first install, i.e. not on-demand. Note " + + "that the dependent modules will also be extracted. The value of this " + + "flag is ignored if the device receives a standalone APK.", + ALL_MODULES_SHORTCUT) .build()) .addFlag( FlagDescription.builder() 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 1dd7316b..59190f97 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 @@ -23,14 +23,14 @@ import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; import com.android.tools.build.bundletool.device.AdbServer; import com.android.tools.build.bundletool.device.DeviceAnalyzer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.SdkToolsLocator; -import com.android.tools.build.bundletool.utils.SystemEnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.files.FilePreconditions; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; +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.DefaultSystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.utils.SdkToolsLocator; +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.utils.files.FilePreconditions; import com.google.auto.value.AutoValue; import com.google.common.io.MoreFiles; import com.google.protobuf.util.JsonFormat; @@ -55,8 +55,8 @@ public abstract class GetDeviceSpecCommand { private static final String ANDROID_HOME_VARIABLE = "ANDROID_HOME"; private static final String ANDROID_SERIAL_VARIABLE = "ANDROID_SERIAL"; - private static final EnvironmentVariableProvider DEFAULT_PROVIDER = - new SystemEnvironmentVariableProvider(); + private static final SystemEnvironmentProvider DEFAULT_PROVIDER = + new DefaultSystemEnvironmentProvider(); private static final String JSON_EXTENSION = "json"; @@ -116,15 +116,13 @@ public static GetDeviceSpecCommand fromFlags(ParsedFlags flags, AdbServer adbSer } public static GetDeviceSpecCommand fromFlags( - ParsedFlags flags, - EnvironmentVariableProvider environmentVariableProvider, - AdbServer adbServer) { + ParsedFlags flags, SystemEnvironmentProvider systemEnvironmentProvider, AdbServer adbServer) { GetDeviceSpecCommand.Builder builder = builder().setAdbServer(adbServer).setOutputPath(OUTPUT_FLAG.getRequiredValue(flags)); Optional deviceSerialName = DEVICE_ID_FLAG.getValue(flags); if (!deviceSerialName.isPresent()) { - deviceSerialName = environmentVariableProvider.getVariable(ANDROID_SERIAL_VARIABLE); + deviceSerialName = systemEnvironmentProvider.getVariable(ANDROID_SERIAL_VARIABLE); } deviceSerialName.ifPresent(builder::setDeviceId); @@ -133,7 +131,7 @@ public static GetDeviceSpecCommand fromFlags( .getValue(flags) .orElseGet( () -> - environmentVariableProvider + systemEnvironmentProvider .getVariable(ANDROID_HOME_VARIABLE) .flatMap(path -> new SdkToolsLocator().locateAdb(Paths.get(path))) .orElseThrow( 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 f813b663..f7e0cce2 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 @@ -16,12 +16,14 @@ package com.android.tools.build.bundletool.commands; +import static com.android.tools.build.bundletool.commands.GetSizeCommand.GetSizeSubcommand.STRING_TO_SUBCOMMAND; import static com.android.tools.build.bundletool.commands.GetSizeCommand.GetSizeSubcommand.TOTAL; -import static com.android.tools.build.bundletool.utils.ApkSizeUtils.getCompressedSizeByApkPaths; -import static com.android.tools.build.bundletool.utils.CollectorUtils.combineMaps; -import static com.android.tools.build.bundletool.utils.GetSizeCsvUtils.getSizeTotalOutputInCsv; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; -import static java.util.stream.Collectors.toList; +import static com.android.tools.build.bundletool.model.utils.ApkSizeUtils.getCompressedSizeByApkPaths; +import static com.android.tools.build.bundletool.model.utils.CollectorUtils.combineMaps; +import static com.android.tools.build.bundletool.model.utils.GetSizeCsvUtils.getSizeTotalOutputInCsv; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static java.util.function.Function.identity; import com.android.bundle.Commands.BuildApksResult; import com.android.bundle.Commands.Variant; @@ -30,15 +32,16 @@ import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; import com.android.tools.build.bundletool.device.DeviceSpecParser; import com.android.tools.build.bundletool.device.VariantMatcher; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.device.VariantTotalSizeAggregator; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.model.ConfigurationSizes; import com.android.tools.build.bundletool.model.GetSizeRequest; +import com.android.tools.build.bundletool.model.GetSizeRequest.Dimension; import com.android.tools.build.bundletool.model.SizeConfiguration; -import com.android.tools.build.bundletool.utils.ResultUtils; -import com.android.tools.build.bundletool.utils.VariantTotalSizeAggregator; -import com.android.tools.build.bundletool.utils.files.FilePreconditions; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +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.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; @@ -56,18 +59,36 @@ public abstract class GetSizeCommand implements GetSizeRequest { public static final String COMMAND_NAME = "get-size"; - /** Dimensions to expand the sizes in the output against. */ - public enum Dimension { - SDK, - ABI, - SCREEN_DENSITY, - LANGUAGE, - ALL - } - /** Sub commands supported on {@link GetSizeCommand}. */ public enum GetSizeSubcommand { - TOTAL + TOTAL("total"); + + static final ImmutableMap STRING_TO_SUBCOMMAND = + Arrays.stream(GetSizeSubcommand.values()) + .collect(toImmutableMap(GetSizeSubcommand::toString, identity())); + + private final String subCommand; + + GetSizeSubcommand(String subCommand) { + this.subCommand = subCommand; + } + + @Override + public String toString() { + return subCommand; + } + + public static GetSizeSubcommand fromString(String subCommand) { + GetSizeSubcommand result = STRING_TO_SUBCOMMAND.get(subCommand); + if (result == null) { + throw ValidationException.builder() + .withMessage( + "Unrecognized get-size command target: '%s'. Accepted values are: %s", + subCommand, STRING_TO_SUBCOMMAND.keySet()) + .build(); + } + return result; + } } private static final Flag APKS_ARCHIVE_FILE_FLAG = Flag.path("apks"); @@ -173,20 +194,7 @@ private static GetSizeSubcommand parseGetSizeSubCommand(ParsedFlags flags) { .orElseThrow( () -> new ValidationException("Target of the get-size command not found.")); - switch (subCommand) { - case "total": - return TOTAL; - default: - throw ValidationException.builder() - .withMessage( - "Unrecognized get-size command target: '%s'. Accepted values are: %s", - subCommand, - Arrays.stream(GetSizeSubcommand.values()) - .map(Enum::toString) - .map(String::toLowerCase) - .collect(toList())) - .build(); - } + return GetSizeSubcommand.fromString(subCommand); } public void execute() { @@ -230,6 +238,7 @@ ConfigurationSizes getSizeTotalInternal() { public static CommandHelp help() { return CommandHelp.builder() .setCommandName(COMMAND_NAME) + .setSubCommandNames(STRING_TO_SUBCOMMAND.keySet().asList()) .setCommandDescription( CommandDescription.builder() .setShortDescription( diff --git a/src/main/java/com/android/tools/build/bundletool/commands/InstallApksCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/InstallApksCommand.java index 326bf1fd..40c89742 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/InstallApksCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/InstallApksCommand.java @@ -16,9 +16,9 @@ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkDirectoryExists; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndExecutable; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkDirectoryExists; +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 com.android.bundle.Devices.DeviceSpec; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; @@ -27,13 +27,13 @@ import com.android.tools.build.bundletool.device.ApksInstaller; import com.android.tools.build.bundletool.device.Device.InstallOptions; import com.android.tools.build.bundletool.device.DeviceAnalyzer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.io.TempFiles; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.SdkToolsLocator; -import com.android.tools.build.bundletool.utils.SystemEnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.DefaultSystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.utils.SdkToolsLocator; +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -57,8 +57,8 @@ public abstract class InstallApksCommand { private static final String ANDROID_HOME_VARIABLE = "ANDROID_HOME"; private static final String ANDROID_SERIAL_VARIABLE = "ANDROID_SERIAL"; - private static final EnvironmentVariableProvider DEFAULT_PROVIDER = - new SystemEnvironmentVariableProvider(); + private static final SystemEnvironmentProvider DEFAULT_PROVIDER = + new DefaultSystemEnvironmentProvider(); public abstract Path getAdbPath(); @@ -100,16 +100,14 @@ public static InstallApksCommand fromFlags(ParsedFlags flags, AdbServer adbServe } public static InstallApksCommand fromFlags( - ParsedFlags flags, - EnvironmentVariableProvider environmentVariableProvider, - AdbServer adbServer) { + ParsedFlags flags, SystemEnvironmentProvider systemEnvironmentProvider, AdbServer adbServer) { Path apksArchivePath = APKS_ARCHIVE_FILE_FLAG.getRequiredValue(flags); Path adbPath = ADB_PATH_FLAG .getValue(flags) .orElseGet( () -> - environmentVariableProvider + systemEnvironmentProvider .getVariable(ANDROID_HOME_VARIABLE) .flatMap(path -> new SdkToolsLocator().locateAdb(Paths.get(path))) .orElseThrow( @@ -120,7 +118,7 @@ public static InstallApksCommand fromFlags( Optional deviceSerialName = DEVICE_ID_FLAG.getValue(flags); if (!deviceSerialName.isPresent()) { - deviceSerialName = environmentVariableProvider.getVariable(ANDROID_SERIAL_VARIABLE); + deviceSerialName = systemEnvironmentProvider.getVariable(ANDROID_SERIAL_VARIABLE); } Optional> modules = MODULES_FLAG.getValue(flags); @@ -236,9 +234,11 @@ public static CommandHelp help() { .setExampleValue("base,module1,module2") .setOptional(true) .setDescription( - "List of modules to be installed (defaults to all of them). Note that the " - + "dependent modules will also be installed. Ignored if the device " - + "receives a standalone APK.") + "List of modules to be installed, or \"%s\" for all modules. Defaults to " + + "modules installed during first install, i.e. not on-demand. Note that " + + "the dependent modules will also be installed. The value of this flag is " + + "ignored if the device receives a standalone APK.", + ExtractApksCommand.ALL_MODULES_SHORTCUT) .build()) .build(); } diff --git a/src/main/java/com/android/tools/build/bundletool/commands/ValidateBundleCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/ValidateBundleCommand.java index a35d4bab..e683cee2 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/ValidateBundleCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/ValidateBundleCommand.java @@ -16,17 +16,17 @@ package com.android.tools.build.bundletool.commands; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.flags.Flag; +import com.android.tools.build.bundletool.flags.ParsedFlags; 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.ModuleEntry; -import com.android.tools.build.bundletool.utils.flags.Flag; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.validation.AppBundleValidator; import com.google.auto.value.AutoValue; import java.io.IOException; @@ -95,11 +95,20 @@ private void validateInput() { private void printBundleSummary(AppBundle appBundle) { System.out.printf("App Bundle information\n"); System.out.printf("------------\n"); - System.out.printf("Modules:\n"); - for (Entry moduleEntry : appBundle.getModules().entrySet()) { - System.out.printf("\tModule: %s\n", moduleEntry.getKey()); + System.out.printf("Feature modules:\n"); + for (Entry moduleEntry : + appBundle.getFeatureModules().entrySet()) { + System.out.printf("\tFeature module: %s\n", moduleEntry.getKey()); printModuleSummary(moduleEntry.getValue()); } + if (!appBundle.getAssetModules().isEmpty()) { + System.out.printf("Remote asset modules:\n"); + for (Entry moduleEntry : + appBundle.getAssetModules().entrySet()) { + System.out.printf("\tRemote asset module: %s\n", moduleEntry.getKey()); + printModuleSummary(moduleEntry.getValue()); + } + } } private void printModuleSummary(BundleModule bundleModule) { diff --git a/src/main/java/com/android/tools/build/bundletool/commands/VersionCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/VersionCommand.java index eaa2ec08..d3621734 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/VersionCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/VersionCommand.java @@ -16,8 +16,8 @@ package com.android.tools.build.bundletool.commands; import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import java.io.PrintStream; /** Command to print the version of BundleTool. */ diff --git a/src/main/java/com/android/tools/build/bundletool/device/AbiMatcher.java b/src/main/java/com/android/tools/build/bundletool/device/AbiMatcher.java index 00b81716..d4c72cf8 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/AbiMatcher.java +++ b/src/main/java/com/android/tools/build/bundletool/device/AbiMatcher.java @@ -25,9 +25,9 @@ import com.android.bundle.Targeting.AbiTargeting; import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; diff --git a/src/main/java/com/android/tools/build/bundletool/device/AdbServer.java b/src/main/java/com/android/tools/build/bundletool/device/AdbServer.java index d0ede7bb..a432b770 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/AdbServer.java +++ b/src/main/java/com/android/tools/build/bundletool/device/AdbServer.java @@ -18,7 +18,7 @@ import static java.lang.Thread.sleep; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import java.io.Closeable; diff --git a/src/main/java/com/android/tools/build/bundletool/device/AdbShellCommandTask.java b/src/main/java/com/android/tools/build/bundletool/device/AdbShellCommandTask.java index 1fe8b722..6d2ca46c 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/AdbShellCommandTask.java +++ b/src/main/java/com/android/tools/build/bundletool/device/AdbShellCommandTask.java @@ -20,7 +20,7 @@ import com.android.ddmlib.MultiLineReceiver; import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.TimeoutException; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.concurrent.TimeUnit; 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 2b02d7af..dccd7c9f 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 @@ -26,10 +26,10 @@ import com.android.bundle.Commands.Variant; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.ApkTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.ModuleSplit; 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.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; diff --git a/src/main/java/com/android/tools/build/bundletool/device/ApksInstaller.java b/src/main/java/com/android/tools/build/bundletool/device/ApksInstaller.java index 5215ec2c..0fe36f8e 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/ApksInstaller.java +++ b/src/main/java/com/android/tools/build/bundletool/device/ApksInstaller.java @@ -19,9 +19,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.tools.build.bundletool.device.Device.InstallOptions; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.DeviceNotFoundException; -import com.android.tools.build.bundletool.exceptions.DeviceNotFoundException.TooManyDevicesMatchedException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.DeviceNotFoundException; +import com.android.tools.build.bundletool.model.exceptions.DeviceNotFoundException.TooManyDevicesMatchedException; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import java.nio.file.Path; diff --git a/src/main/java/com/android/tools/build/bundletool/device/DdmlibAdbServer.java b/src/main/java/com/android/tools/build/bundletool/device/DdmlibAdbServer.java index 44a7c98c..c6b6b62d 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/DdmlibAdbServer.java +++ b/src/main/java/com/android/tools/build/bundletool/device/DdmlibAdbServer.java @@ -20,7 +20,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.ddmlib.AndroidDebugBridge; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.concurrent.GuardedBy; import java.nio.file.Path; 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 0459f41d..1cc07ac9 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 @@ -26,7 +26,7 @@ import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.TimeoutException; import com.android.sdklib.AndroidVersion; -import com.android.tools.build.bundletool.exceptions.InstallationException; +import com.android.tools.build.bundletool.model.exceptions.InstallationException; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.io.File; diff --git a/src/main/java/com/android/tools/build/bundletool/device/Device.java b/src/main/java/com/android/tools/build/bundletool/device/Device.java index 65cf1125..e4d4f9b3 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/Device.java +++ b/src/main/java/com/android/tools/build/bundletool/device/Device.java @@ -24,6 +24,7 @@ import com.android.sdklib.AndroidVersion; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; import java.io.IOException; import java.nio.file.Path; import java.time.Duration; @@ -61,7 +62,9 @@ public abstract void executeShellCommand( public abstract void installApks(ImmutableList apks, InstallOptions installOptions); /** Options related to APK installation. */ + @Immutable @AutoValue + @AutoValue.CopyAnnotations public abstract static class InstallOptions { public abstract boolean getAllowDowngrade(); diff --git a/src/main/java/com/android/tools/build/bundletool/device/DeviceAnalyzer.java b/src/main/java/com/android/tools/build/bundletool/device/DeviceAnalyzer.java index 441446ee..fbc767d5 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/DeviceAnalyzer.java +++ b/src/main/java/com/android/tools/build/bundletool/device/DeviceAnalyzer.java @@ -21,8 +21,8 @@ import com.android.bundle.Devices.DeviceSpec; import com.android.ddmlib.IDevice.DeviceState; import com.android.tools.build.bundletool.device.activitymanager.ActivityManagerRunner; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.utils.Versions; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.Versions; import com.google.common.collect.ImmutableList; import java.util.Optional; import java.util.concurrent.TimeoutException; diff --git a/src/main/java/com/android/tools/build/bundletool/device/DeviceFeaturesParser.java b/src/main/java/com/android/tools/build/bundletool/device/DeviceFeaturesParser.java index d571c6a9..fb548787 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/DeviceFeaturesParser.java +++ b/src/main/java/com/android/tools/build/bundletool/device/DeviceFeaturesParser.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.device; -import com.android.tools.build.bundletool.exceptions.ParseException; +import com.android.tools.build.bundletool.model.exceptions.ParseException; import com.google.common.collect.ImmutableList; /** Parses the output of the "pm list features" ADB shell command. */ diff --git a/src/main/java/com/android/tools/build/bundletool/device/DeviceSpecParser.java b/src/main/java/com/android/tools/build/bundletool/device/DeviceSpecParser.java index 1ba1ee87..7516e3a2 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/DeviceSpecParser.java +++ b/src/main/java/com/android/tools/build/bundletool/device/DeviceSpecParser.java @@ -17,8 +17,8 @@ package com.android.tools.build.bundletool.device; import com.android.bundle.Devices.DeviceSpec; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.common.io.MoreFiles; import com.google.protobuf.util.JsonFormat; import java.io.IOException; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/DeviceSpecUtils.java b/src/main/java/com/android/tools/build/bundletool/device/DeviceSpecUtils.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/DeviceSpecUtils.java rename to src/main/java/com/android/tools/build/bundletool/device/DeviceSpecUtils.java index 45257756..fe50fa1a 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/DeviceSpecUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/device/DeviceSpecUtils.java @@ -14,9 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.device; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.getScreenDensityDpi; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.getScreenDensityDpi; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.AbiTargeting; diff --git a/src/main/java/com/android/tools/build/bundletool/device/LanguageMatcher.java b/src/main/java/com/android/tools/build/bundletool/device/LanguageMatcher.java index fbe87e24..dc68350d 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/LanguageMatcher.java +++ b/src/main/java/com/android/tools/build/bundletool/device/LanguageMatcher.java @@ -22,7 +22,7 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.LanguageTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.utils.ResourcesUtils; +import com.android.tools.build.bundletool.model.utils.ResourcesUtils; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Set; diff --git a/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java b/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java index b92d6638..67c644a1 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java +++ b/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.device; -import static com.android.tools.build.bundletool.targeting.TargetingComparators.MULTI_ABI_ALIAS_COMPARATOR; +import static com.android.tools.build.bundletool.model.targeting.TargetingComparators.MULTI_ABI_ALIAS_COMPARATOR; import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Devices.DeviceSpec; @@ -26,9 +26,9 @@ import com.android.bundle.Targeting.MultiAbi; import com.android.bundle.Targeting.MultiAbiTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Streams; diff --git a/src/main/java/com/android/tools/build/bundletool/device/ScreenDensityMatcher.java b/src/main/java/com/android/tools/build/bundletool/device/ScreenDensityMatcher.java index 107547f4..c24cca30 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/ScreenDensityMatcher.java +++ b/src/main/java/com/android/tools/build/bundletool/device/ScreenDensityMatcher.java @@ -23,8 +23,8 @@ import com.android.bundle.Targeting.ScreenDensity; import com.android.bundle.Targeting.ScreenDensityTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.targeting.ScreenDensitySelector; -import com.android.tools.build.bundletool.utils.ResourcesUtils; +import com.android.tools.build.bundletool.model.targeting.ScreenDensitySelector; +import com.android.tools.build.bundletool.model.utils.ResourcesUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; diff --git a/src/main/java/com/android/tools/build/bundletool/device/SdkVersionMatcher.java b/src/main/java/com/android/tools/build/bundletool/device/SdkVersionMatcher.java index ff5433a2..9c4e8459 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/SdkVersionMatcher.java +++ b/src/main/java/com/android/tools/build/bundletool/device/SdkVersionMatcher.java @@ -24,7 +24,7 @@ import com.android.bundle.Targeting.SdkVersion; import com.android.bundle.Targeting.SdkVersionTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import java.util.stream.Stream; /** A {@link TargetingDimensionMatcher} that provides matching on SDK version. */ diff --git a/src/main/java/com/android/tools/build/bundletool/utils/VariantTotalSizeAggregator.java b/src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java similarity index 91% rename from src/main/java/com/android/tools/build/bundletool/utils/VariantTotalSizeAggregator.java rename to src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java index 98d70f3f..812fe77e 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/VariantTotalSizeAggregator.java +++ b/src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java @@ -14,17 +14,17 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; - -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.ABI; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.LANGUAGE; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.SCREEN_DENSITY; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.SDK; -import static com.android.tools.build.bundletool.utils.DeviceSpecUtils.isAbiMissing; -import static com.android.tools.build.bundletool.utils.DeviceSpecUtils.isLocalesMissing; -import static com.android.tools.build.bundletool.utils.DeviceSpecUtils.isScreenDensityMissing; -import static com.android.tools.build.bundletool.utils.DeviceSpecUtils.isSdkVersionMissing; -import static com.android.tools.build.bundletool.utils.ResultUtils.isStandaloneApkVariant; +package com.android.tools.build.bundletool.device; + +import static com.android.tools.build.bundletool.device.DeviceSpecUtils.isAbiMissing; +import static com.android.tools.build.bundletool.device.DeviceSpecUtils.isLocalesMissing; +import static com.android.tools.build.bundletool.device.DeviceSpecUtils.isScreenDensityMissing; +import static com.android.tools.build.bundletool.device.DeviceSpecUtils.isSdkVersionMissing; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.ABI; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.LANGUAGE; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.SCREEN_DENSITY; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.SDK; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.isStandaloneApkVariant; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -39,13 +39,12 @@ import com.android.bundle.Targeting.SdkVersionTargeting; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.commands.GetSizeCommand; -import com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension; -import com.android.tools.build.bundletool.device.ApkMatcher; +import com.android.tools.build.bundletool.device.DeviceSpecUtils.DeviceSpecFromTargetingBuilder; import com.android.tools.build.bundletool.model.ConfigurationSizes; import com.android.tools.build.bundletool.model.GetSizeRequest; +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.utils.DeviceSpecUtils.DeviceSpecFromTargetingBuilder; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -56,7 +55,7 @@ /** * Get total size (min and max) for variant, for each {@link SizeConfiguration} based on the - * dimensions passed to {@link GetSizeCommand}. + * requested dimensions passed to {@link GetSizeCommand}. */ public class VariantTotalSizeAggregator { diff --git a/src/main/java/com/android/tools/build/bundletool/device/activitymanager/AbiStringParser.java b/src/main/java/com/android/tools/build/bundletool/device/activitymanager/AbiStringParser.java index bb8d786a..6c0f05f4 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/activitymanager/AbiStringParser.java +++ b/src/main/java/com/android/tools/build/bundletool/device/activitymanager/AbiStringParser.java @@ -19,8 +19,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.AbiName; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.Optional; diff --git a/src/main/java/com/android/tools/build/bundletool/device/activitymanager/ActivityManagerRunner.java b/src/main/java/com/android/tools/build/bundletool/device/activitymanager/ActivityManagerRunner.java index 569bba98..df712edf 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/activitymanager/ActivityManagerRunner.java +++ b/src/main/java/com/android/tools/build/bundletool/device/activitymanager/ActivityManagerRunner.java @@ -21,7 +21,7 @@ import com.android.tools.build.bundletool.device.AdbShellCommandTask; import com.android.tools.build.bundletool.device.Device; import com.android.tools.build.bundletool.device.activitymanager.ResourceConfigParser.ResourceConfigHandler; -import com.android.tools.build.bundletool.utils.Versions; +import com.android.tools.build.bundletool.model.utils.Versions; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/flags/Flag.java b/src/main/java/com/android/tools/build/bundletool/flags/Flag.java similarity index 90% rename from src/main/java/com/android/tools/build/bundletool/utils/flags/Flag.java rename to src/main/java/com/android/tools/build/bundletool/flags/Flag.java index 8396c191..f929aefb 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/flags/Flag.java +++ b/src/main/java/com/android/tools/build/bundletool/flags/Flag.java @@ -14,20 +14,19 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.flags; +package com.android.tools.build.bundletool.flags; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.google.common.base.Predicates.not; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.nio.charset.StandardCharsets.UTF_8; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.flags.FlagParser.FlagParseException; +import com.android.tools.build.bundletool.model.Password; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.OsPlatform; -import com.android.tools.build.bundletool.utils.flags.FlagParser.FlagParseException; -import com.google.common.annotations.VisibleForTesting; +import com.android.tools.build.bundletool.model.utils.OsPlatform; import com.google.common.base.MoreObjects; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; @@ -35,6 +34,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.io.Files; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyStore.PasswordProtection; @@ -43,7 +43,6 @@ import java.util.Map; import java.util.Optional; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -314,14 +313,18 @@ public final Optional> getValue(ParsedFlags flags) { } private ImmutableMap parseValues(ImmutableList rawValues) { - return rawValues - .stream() + return rawValues.stream() .filter(not(String::isEmpty)) .map(keyValueFlag::parse) .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); } } + /** + * Passwords can be passed in clear (using "pass:" prefix) or via a file (using "file:" prefix). + * + *

This is to match the behaviour from "apksigner". + */ static class PasswordFlag extends SingleValueFlag { public PasswordFlag(String name) { @@ -330,7 +333,30 @@ public PasswordFlag(String name) { @Override protected Password parse(String value) { - return Password.createFromFlagValue(value); + return createFromFlagValue(value); + } + + private static Password createFromFlagValue(String flagValue) { + if (flagValue.startsWith("pass:")) { + return new Password( + () -> new PasswordProtection(flagValue.substring("pass:".length()).toCharArray())); + } else if (flagValue.startsWith("file:")) { + Path passwordFile = Paths.get(flagValue.substring("file:".length())); + checkFileExistsAndReadable(passwordFile); + return new Password( + () -> new PasswordProtection(readPasswordFromFile(passwordFile).toCharArray())); + } + + throw new FlagParseException("Passwords must be prefixed with \"pass:\" or \"file:\"."); + } + + private static String readPasswordFromFile(Path passwordFile) { + try { + return Files.asCharSource(passwordFile.toFile(), UTF_8).readFirstLine(); + } catch (IOException e) { + throw new UncheckedIOException( + String.format("Unable to read password from file '%s'.", passwordFile), e); + } } } @@ -395,8 +421,7 @@ public final Optional> getValue(ParsedFlags flags) { } private ImmutableList parseValues(ImmutableList rawValues) { - return rawValues - .stream() + return rawValues.stream() .filter(not(String::isEmpty)) .map(singleFlag::parse) .collect(toImmutableList()); @@ -417,9 +442,7 @@ protected ImmutableList parse(String value) { if (value.isEmpty()) { return ImmutableList.of(); } - return ITEM_SPLITTER - .splitToList(value) - .stream() + return ITEM_SPLITTER.splitToList(value).stream() .map(singleFlag::parse) .collect(toImmutableList()); } @@ -443,9 +466,7 @@ protected ImmutableSet parse(String value) { if (value.isEmpty()) { return ImmutableSet.of(); } - return ITEM_SPLITTER - .splitToList(value) - .stream() + return ITEM_SPLITTER.splitToList(value).stream() .map(singleFlag::parse) .collect(toImmutableSet()); } @@ -477,50 +498,4 @@ public static final class RequiredFlagNotSetException extends FlagParseException super(String.format("Missing the required --%s flag.", flagName)); } } - - /** - * Password entered by a user on the command line. - * - *

Password can be passed in clear (using "pass:" prefix) or via a file (using "file:" prefix). - * This is to match the behaviour from "apksigner". - */ - public static final class Password { - - private final Supplier passwordSupplier; - - @VisibleForTesting - public static Password createFromFlagValue(String flagValue) { - if (flagValue.startsWith("pass:")) { - return new Password( - () -> new PasswordProtection(flagValue.substring("pass:".length()).toCharArray())); - } else if (flagValue.startsWith("file:")) { - Path passwordFile = Paths.get(flagValue.substring("file:".length())); - checkFileExistsAndReadable(passwordFile); - return new Password( - () -> new PasswordProtection(readPasswordFromFile(passwordFile).toCharArray())); - } - - throw new FlagParseException("Passwords must be prefixed with \"pass:\" or \"file:\"."); - } - - private Password(Supplier passwordSupplier) { - this.passwordSupplier = passwordSupplier; - } - - /** Special note: It's the responsibility of the caller to destroy the password once used. */ - public final PasswordProtection getValue() { - return passwordSupplier.get(); - } - - private static String readPasswordFromFile(Path passwordFile) { - try { - return Files.asCharSource(passwordFile.toFile(), UTF_8).readFirstLine(); - } catch (IOException e) { - throw CommandExecutionException.builder() - .withCause(e) - .withMessage("Unable to read password from file '%s'.", passwordFile) - .build(); - } - } - } } diff --git a/src/main/java/com/android/tools/build/bundletool/utils/flags/FlagParser.java b/src/main/java/com/android/tools/build/bundletool/flags/FlagParser.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/flags/FlagParser.java rename to src/main/java/com/android/tools/build/bundletool/flags/FlagParser.java index cdb1b794..b89f4d54 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/flags/FlagParser.java +++ b/src/main/java/com/android/tools/build/bundletool/flags/FlagParser.java @@ -14,9 +14,10 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.flags; +package com.android.tools.build.bundletool.flags; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import java.util.ArrayList; import java.util.Arrays; @@ -55,7 +56,7 @@ public ParsedFlags parse(String... args) { commands.add(argsToProcess.get(0)); argsToProcess.remove(0); } - return ParsedFlags.create(commands, parseFlags(argsToProcess)); + return new ParsedFlags(ImmutableList.copyOf(commands), parseFlags(argsToProcess)); } private ImmutableListMultimap parseFlags(List args) { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/flags/ParsedFlags.java b/src/main/java/com/android/tools/build/bundletool/flags/ParsedFlags.java similarity index 77% rename from src/main/java/com/android/tools/build/bundletool/utils/flags/ParsedFlags.java rename to src/main/java/com/android/tools/build/bundletool/flags/ParsedFlags.java index 30cddd19..4ae7b766 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/flags/ParsedFlags.java +++ b/src/main/java/com/android/tools/build/bundletool/flags/ParsedFlags.java @@ -14,18 +14,16 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.flags; +package com.android.tools.build.bundletool.flags; import static java.util.stream.Collectors.joining; -import com.android.tools.build.bundletool.utils.flags.FlagParser.FlagParseException; -import com.google.auto.value.AutoValue; +import com.android.tools.build.bundletool.flags.FlagParser.FlagParseException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Sets; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.Set; @@ -44,21 +42,20 @@ * } * */ -@AutoValue -public abstract class ParsedFlags { +public final class ParsedFlags { private final Set accessedFlags = new HashSet<>(); + private final ImmutableList commands; + private final ImmutableListMultimap flags; - static ParsedFlags create(List commands, ImmutableListMultimap flags) { - return new AutoValue_ParsedFlags(commands, flags); + ParsedFlags(ImmutableList commands, ImmutableListMultimap flags) { + this.commands = commands; + this.flags = flags; } - /** - * Returns the list of commands that were parsed. - * - * @return a list of the commands specified on the command line - */ - public abstract List getCommands(); + public ImmutableList getCommands() { + return commands; + } /** * Returns the first command provided on the command line if provided. @@ -81,11 +78,9 @@ public Optional getSubCommand() { } private Optional getSubCommand(int index) { - return getCommands().size() > index ? Optional.of(getCommands().get(index)) : Optional.empty(); + return index < commands.size() ? Optional.of(commands.get(index)) : Optional.empty(); } - protected abstract ImmutableListMultimap getFlags(); - /** * Gets value of the flag, if it has been set. * @@ -105,11 +100,11 @@ Optional getFlagValue(String name) { ImmutableList getFlagValues(String name) { accessedFlags.add(name); - return getFlags().get(name); + return flags.get(name); } public void checkNoUnknownFlags() { - Set unknownFlags = Sets.difference(getFlags().keySet(), accessedFlags); + Set unknownFlags = Sets.difference(flags.keySet(), accessedFlags); if (!unknownFlags.isEmpty()) { throw new UnknownFlagsException(unknownFlags); } diff --git a/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerHelper.java b/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerHelper.java index 46ae4765..afb74feb 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerHelper.java +++ b/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerHelper.java @@ -18,11 +18,10 @@ import static com.android.tools.build.bundletool.model.BundleModule.APEX_DIRECTORY; import static com.android.tools.build.bundletool.model.BundleModule.DEX_DIRECTORY; import static com.android.tools.build.bundletool.model.BundleModule.MANIFEST_FILENAME; -import static com.android.tools.build.bundletool.model.BundleModule.RESOURCES_PROTO_PATH; import static com.android.tools.build.bundletool.model.BundleModule.ROOT_DIRECTORY; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileDoesNotExist; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileHasExtension; -import static com.android.tools.build.bundletool.utils.files.FileUtils.createParentDirectories; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileDoesNotExist; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileHasExtension; +import static com.android.tools.build.bundletool.model.utils.files.FileUtils.createParentDirectories; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -36,15 +35,17 @@ import com.android.tools.build.apkzlib.zip.AlignmentRules; import com.android.tools.build.apkzlib.zip.ZFile; import com.android.tools.build.apkzlib.zip.ZFileOptions; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.io.ZipBuilder.EntryOption; import com.android.tools.build.bundletool.model.Aapt2Command; +import com.android.tools.build.bundletool.model.BundleModule.SpecialModuleEntry; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.SigningConfiguration; import com.android.tools.build.bundletool.model.WearApkLocator; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.files.FileUtils; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.files.FileUtils; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; import com.google.common.base.Predicates; @@ -101,7 +102,7 @@ final class ApkSerializerHelper { private static final Predicate FILES_FOR_AAPT2 = path -> path.startsWith("res") - || path.equals(RESOURCES_PROTO_PATH) + || path.equals(SpecialModuleEntry.RESOURCE_TABLE.getPath()) || path.equals(ZipPath.create(MANIFEST_FILENAME)); private static final String BUILT_BY = "BundleTool"; @@ -113,14 +114,17 @@ final class ApkSerializerHelper { "rtttl", "smf", "wav", "webm", "wma", "wmv", "xmf"); private final Aapt2Command aapt2Command; + private final Version bundleVersion; private final Optional signingConfig; private final ImmutableList uncompressedPathMatchers; ApkSerializerHelper( Aapt2Command aapt2Command, Optional signingConfig, + Version bundleVersion, Compression compression) { this.aapt2Command = aapt2Command; + this.bundleVersion = bundleVersion; this.signingConfig = signingConfig; // Using the default filesystem will work on Windows because the "/" of the glob are swapped @@ -243,7 +247,7 @@ private Path writeProtoApk(ModuleSplit split, Path outputPath, Path tempDir) { Path signedWearApk = signWearApk(entry, signingConfig.get(), tempDir); zipBuilder.addFileFromDisk(pathInApk, signedWearApk.toFile(), entryOptions); } else { - zipBuilder.addFile(pathInApk, () -> entry.getContent(), entryOptions); + zipBuilder.addFile(pathInApk, entry.getContentSupplier(), entryOptions); } } @@ -251,7 +255,8 @@ private Path writeProtoApk(ModuleSplit split, Path outputPath, Path tempDir) { .getResourceTable() .ifPresent( resourceTable -> - zipBuilder.addFileWithProtoContent(RESOURCES_PROTO_PATH, resourceTable)); + zipBuilder.addFileWithProtoContent( + SpecialModuleEntry.RESOURCE_TABLE.getPath(), resourceTable)); zipBuilder.addFileWithProtoContent( ZipPath.create(MANIFEST_FILENAME), split.getAndroidManifest().getManifestRoot().getProto()); @@ -288,8 +293,12 @@ private boolean shouldCompress( return false; } - // Common extensions that should remain uncompressed because don't provide any gains. - if (NO_COMPRESSION_EXTENSIONS.contains(FileUtils.getFileExtension(path))) { + // Common extensions that should remain uncompressed because compression doesn't provide any + // gains. + // For bundle versions starting by 0.7.3 the no-compression is fully configured through the + // bundle config file. + if (bundleVersion.isOlderThan(Version.of("0.7.3")) + && NO_COMPRESSION_EXTENSIONS.contains(FileUtils.getFileExtension(path))) { return false; } @@ -370,7 +379,9 @@ private static Path signWearApk( // Input Path unsignedApk = tempDir.resolve("wear-unsigned.apk"); - Files.copy(wearApkEntry.getContent(), unsignedApk); + try (InputStream entryContent = wearApkEntry.getContent()) { + Files.copy(entryContent, unsignedApk); + } // Output Path signedApk = tempDir.resolve("wear-signed.apk"); 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 c3ea3eba..91fb8fb0 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,7 +15,7 @@ */ package com.android.tools.build.bundletool.io; -import static com.android.tools.build.bundletool.utils.CollectorUtils.groupingBySortedKeys; +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.Predicates.alwaysTrue; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -39,7 +39,7 @@ import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.VariantKey; -import com.android.tools.build.bundletool.utils.ConcurrencyUtils; +import com.android.tools.build.bundletool.model.utils.ConcurrencyUtils; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -78,8 +78,8 @@ public ApkSerializerManager( } public ImmutableList serializeApksForDevice( - GeneratedApks generatedApks, DeviceSpec deviceSpec) { - return serializeApks(generatedApks, ApkBuildMode.DEFAULT, Optional.of(deviceSpec)); + GeneratedApks generatedApks, DeviceSpec deviceSpec, ApkBuildMode apkBuildMode) { + return serializeApks(generatedApks, apkBuildMode, Optional.of(deviceSpec)); } @VisibleForTesting diff --git a/src/main/java/com/android/tools/build/bundletool/io/ApkSetBuilderFactory.java b/src/main/java/com/android/tools/build/bundletool/io/ApkSetBuilderFactory.java index b83be29e..b64500d3 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/ApkSetBuilderFactory.java +++ b/src/main/java/com/android/tools/build/bundletool/io/ApkSetBuilderFactory.java @@ -16,15 +16,15 @@ package com.android.tools.build.bundletool.io; -import static com.android.tools.build.bundletool.utils.FileNames.TABLE_OF_CONTENTS_FILE; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.FileNames.TABLE_OF_CONTENTS_FILE; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import com.android.bundle.Commands.ApkDescription; import com.android.bundle.Commands.BuildApksResult; import com.android.tools.build.bundletool.io.ZipBuilder.EntryOption; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.common.collect.ImmutableList; import com.google.protobuf.Message; import java.io.FileNotFoundException; diff --git a/src/main/java/com/android/tools/build/bundletool/io/AppBundleSerializer.java b/src/main/java/com/android/tools/build/bundletool/io/AppBundleSerializer.java index d90d3a0b..b41cf635 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/AppBundleSerializer.java +++ b/src/main/java/com/android/tools/build/bundletool/io/AppBundleSerializer.java @@ -19,11 +19,14 @@ import static com.android.tools.build.bundletool.model.AppBundle.BUNDLE_CONFIG_FILE_NAME; import static com.android.tools.build.bundletool.model.AppBundle.METADATA_DIRECTORY; +import com.android.tools.build.bundletool.io.ZipBuilder.EntryOption; import com.android.tools.build.bundletool.model.AppBundle; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModule.SpecialModuleEntry; import com.android.tools.build.bundletool.model.InputStreamSupplier; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.google.common.base.Preconditions; import java.io.IOException; import java.nio.file.Path; import java.util.Map.Entry; @@ -31,23 +34,44 @@ /** Serializer of Bundle instances onto disk. */ public class AppBundleSerializer { + /** Set to true if all entries should be left uncompressed in the bundle. */ + private final boolean allEntriesUncompressed; + + public AppBundleSerializer(boolean allEntriesUncompressed) { + this.allEntriesUncompressed = allEntriesUncompressed; + } + + public AppBundleSerializer() { + this(/* allEntriesUncompressed= */ false); + } + /** Writes the App Bundle on disk at the given location. */ public void writeToDisk(AppBundle bundle, Path pathOnDisk) throws IOException { + Preconditions.checkState( + bundle.getAssetModules().isEmpty(), + "Writing AssetModules to disk is not yet implemented."); + ZipBuilder zipBuilder = new ZipBuilder(); + EntryOption[] compression = + allEntriesUncompressed ? new EntryOption[] {EntryOption.UNCOMPRESSED} : new EntryOption[0]; + zipBuilder.addFileWithProtoContent( - ZipPath.create(BUNDLE_CONFIG_FILE_NAME), bundle.getBundleConfig()); + ZipPath.create(BUNDLE_CONFIG_FILE_NAME), bundle.getBundleConfig(), compression); // APEX bundles do not have metadata files. - if (bundle.getModules().isEmpty() || !bundle.getBaseModule().getApexConfig().isPresent()) { + if (bundle.getFeatureModules().isEmpty() + || !bundle.getBaseModule().getApexConfig().isPresent()) { for (Entry metadataEntry : bundle.getBundleMetadata().getFileDataMap().entrySet()) { zipBuilder.addFile( - METADATA_DIRECTORY.resolve(metadataEntry.getKey()), metadataEntry.getValue()); + METADATA_DIRECTORY.resolve(metadataEntry.getKey()), + metadataEntry.getValue(), + compression); } } - for (BundleModule module : bundle.getModules().values()) { + for (BundleModule module : bundle.getFeatureModules().values()) { ZipPath moduleDir = ZipPath.create(module.getName().toString()); for (ModuleEntry entry : module.getEntries()) { @@ -55,38 +79,47 @@ public void writeToDisk(AppBundle bundle, Path pathOnDisk) throws IOException { if (entry.isDirectory()) { zipBuilder.addDirectory(entryPath); } else { - zipBuilder.addFile(entryPath, () -> entry.getContent()); + zipBuilder.addFile(entryPath, entry.getContentSupplier(), compression); } } // Special module files are not represented as module entries (above). zipBuilder.addFileWithProtoContent( - moduleDir.resolve(BundleModule.MANIFEST_PATH), - module.getAndroidManifest().getManifestRoot().getProto()); + moduleDir.resolve(SpecialModuleEntry.ANDROID_MANIFEST.getPath()), + module.getAndroidManifest().getManifestRoot().getProto(), + compression); module .getAssetsConfig() .ifPresent( assetsConfig -> zipBuilder.addFileWithProtoContent( - moduleDir.resolve(BundleModule.ASSETS_PROTO_PATH), assetsConfig)); + moduleDir.resolve(SpecialModuleEntry.ASSETS_TABLE.getPath()), + assetsConfig, + compression)); module .getNativeConfig() .ifPresent( nativeConfig -> zipBuilder.addFileWithProtoContent( - moduleDir.resolve(BundleModule.NATIVE_PROTO_PATH), nativeConfig)); + moduleDir.resolve(SpecialModuleEntry.NATIVE_LIBS_TABLE.getPath()), + nativeConfig, + compression)); module .getResourceTable() .ifPresent( resourceTable -> zipBuilder.addFileWithProtoContent( - moduleDir.resolve(BundleModule.RESOURCES_PROTO_PATH), resourceTable)); + moduleDir.resolve(SpecialModuleEntry.RESOURCE_TABLE.getPath()), + resourceTable, + compression)); module .getApexConfig() .ifPresent( apexConfig -> zipBuilder.addFileWithProtoContent( - moduleDir.resolve(BundleModule.APEX_PROTO_PATH), apexConfig)); + moduleDir.resolve(SpecialModuleEntry.APEX_TABLE.getPath()), + apexConfig, + compression)); } zipBuilder.writeTo(pathOnDisk); diff --git a/src/main/java/com/android/tools/build/bundletool/io/SplitApkSerializer.java b/src/main/java/com/android/tools/build/bundletool/io/SplitApkSerializer.java index 4bd4efd0..77fa1ba4 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/SplitApkSerializer.java +++ b/src/main/java/com/android/tools/build/bundletool/io/SplitApkSerializer.java @@ -26,6 +26,7 @@ import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.SigningConfiguration; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.version.Version; import java.nio.file.Path; import java.util.Optional; import java.util.function.BiFunction; @@ -40,9 +41,11 @@ public SplitApkSerializer( ApkPathManager apkPathManager, Aapt2Command aapt2Command, Optional signingConfig, + Version bundleVersion, Compression compression) { this.apkPathManager = apkPathManager; - this.apkSerializerHelper = new ApkSerializerHelper(aapt2Command, signingConfig, compression); + this.apkSerializerHelper = + new ApkSerializerHelper(aapt2Command, signingConfig, bundleVersion, compression); } /** Writes the installable split to disk. */ diff --git a/src/main/java/com/android/tools/build/bundletool/io/StandaloneApkSerializer.java b/src/main/java/com/android/tools/build/bundletool/io/StandaloneApkSerializer.java index 8bcde9b7..a5c5ff20 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/StandaloneApkSerializer.java +++ b/src/main/java/com/android/tools/build/bundletool/io/StandaloneApkSerializer.java @@ -27,6 +27,7 @@ import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.SigningConfiguration; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import java.nio.file.Path; @@ -42,9 +43,11 @@ public StandaloneApkSerializer( ApkPathManager apkPathManager, Aapt2Command aapt2Command, Optional signingConfig, + Version bundleVersion, Compression compression) { this.apkPathManager = apkPathManager; - this.apkSerializerHelper = new ApkSerializerHelper(aapt2Command, signingConfig, compression); + this.apkSerializerHelper = + new ApkSerializerHelper(aapt2Command, signingConfig, bundleVersion, compression); } public ApkDescription writeToDisk(ModuleSplit standaloneSplit, Path outputDirectory) { diff --git a/src/main/java/com/android/tools/build/bundletool/io/ZipBuilder.java b/src/main/java/com/android/tools/build/bundletool/io/ZipBuilder.java index 065d009d..daa1de0a 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/ZipBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/io/ZipBuilder.java @@ -21,15 +21,17 @@ import com.android.tools.build.bundletool.model.InputStreamSupplier; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteStreams; +import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLite; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Enumeration; @@ -67,7 +69,8 @@ public synchronized Path writeTo(Path target) throws IOException { Path tempFile = Files.createTempFile("ZipBuilder-", ".zip.tmp"); try { - try (ZipOutputStream outZip = new ZipOutputStream(BufferedIo.outputStream(tempFile))) { + try (OutputStream out = BufferedIo.outputStream(tempFile); + ZipOutputStream outZip = new ZipOutputStream(out)) { for (ZipPath path : entries.keySet()) { Entry entry = entries.get(path); if (entry.getIsDirectory()) { @@ -131,7 +134,7 @@ public ZipBuilder addFileWithContent(ZipPath toPath, byte[] content, EntryOption */ public ZipBuilder addFileFromDisk(ZipPath toPath, File file, EntryOption... options) { checkArgument(file.isFile(), "Path '%s' does not denote a file.", file); - return addFile(toPath, () -> BufferedIo.inputStream(file.toPath()), options); + return addFile(toPath, BufferedIo.inputStreamSupplier(file.toPath()), options); } /** @@ -151,7 +154,7 @@ public ZipBuilder addFileWithProtoContent( */ public ZipBuilder addFileFromZip( ZipPath toPath, ZipFile fromZipFile, ZipEntry zipEntry, EntryOption... options) { - return addFile(toPath, () -> BufferedIo.inputStream(fromZipFile, zipEntry), options); + return addFile(toPath, BufferedIo.inputStreamSupplier(fromZipFile, zipEntry), options); } /** @@ -212,7 +215,9 @@ public ZipBuilder copyAllContentsFromZip( /** * Internal data object holding properties of an entry to be written by the {@link ZipBuilder}. */ + @Immutable @AutoValue + @AutoValue.CopyAnnotations protected abstract static class Entry { /** Absent for directory entries. */ public abstract Optional getInputStreamSupplier(); diff --git a/src/main/java/com/android/tools/build/bundletool/mergers/D8DexMerger.java b/src/main/java/com/android/tools/build/bundletool/mergers/D8DexMerger.java index b25b1c0e..4110ddad 100755 --- a/src/main/java/com/android/tools/build/bundletool/mergers/D8DexMerger.java +++ b/src/main/java/com/android/tools/build/bundletool/mergers/D8DexMerger.java @@ -16,12 +16,12 @@ package com.android.tools.build.bundletool.mergers; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkDirectoryExistsAndEmpty; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkDirectoryExistsAndEmpty; import static com.google.common.collect.ImmutableList.toImmutableList; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.utils.ThrowableUtils; -import com.android.tools.build.bundletool.utils.files.FilePreconditions; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.ThrowableUtils; +import com.android.tools.build.bundletool.model.utils.files.FilePreconditions; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.D8; diff --git a/src/main/java/com/android/tools/build/bundletool/mergers/DexMerger.java b/src/main/java/com/android/tools/build/bundletool/mergers/DexMerger.java index 88df7ea8..02bfe972 100755 --- a/src/main/java/com/android/tools/build/bundletool/mergers/DexMerger.java +++ b/src/main/java/com/android/tools/build/bundletool/mergers/DexMerger.java @@ -34,7 +34,8 @@ public interface DexMerger { * file. Specified using format "com/example/MyClass.class", one class name per line. * @param isDebuggable indicates whether the Android app has the 'debuggable' flag set * @return merged dex files - * @throws com.android.tools.build.bundletool.exceptions.CommandExecutionException on failure + * @throws com.android.tools.build.bundletool.model.exceptions.CommandExecutionException on + * failure */ ImmutableList merge( ImmutableList dexFiles, diff --git a/src/main/java/com/android/tools/build/bundletool/mergers/MergingUtils.java b/src/main/java/com/android/tools/build/bundletool/mergers/MergingUtils.java index 7315dae3..75635a29 100755 --- a/src/main/java/com/android/tools/build/bundletool/mergers/MergingUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/mergers/MergingUtils.java @@ -16,10 +16,10 @@ package com.android.tools.build.bundletool.mergers; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.abiUniverse; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.abiValues; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.densityUniverse; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.densityValues; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.abiUniverse; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.abiValues; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.densityUniverse; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.densityValues; import static com.google.common.base.Preconditions.checkNotNull; import com.android.bundle.Targeting.Abi; @@ -27,7 +27,7 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.ScreenDensity; import com.android.bundle.Targeting.ScreenDensityTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.Sets; import java.util.Optional; import java.util.Set; diff --git a/src/main/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMerger.java b/src/main/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMerger.java index ad59a614..55864f78 100755 --- a/src/main/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMerger.java +++ b/src/main/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMerger.java @@ -25,7 +25,6 @@ import com.android.aapt.Resources.ResourceTable; import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModuleName; @@ -35,7 +34,8 @@ import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; diff --git a/src/main/java/com/android/tools/build/bundletool/model/Aapt2Command.java b/src/main/java/com/android/tools/build/bundletool/model/Aapt2Command.java index 9783db60..69bdbf7b 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/Aapt2Command.java +++ b/src/main/java/com/android/tools/build/bundletool/model/Aapt2Command.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.model; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Path; diff --git a/src/main/java/com/android/tools/build/bundletool/model/AbiName.java b/src/main/java/com/android/tools/build/bundletool/model/AbiName.java index 1e462000..bbcbbb0e 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/AbiName.java +++ b/src/main/java/com/android/tools/build/bundletool/model/AbiName.java @@ -23,7 +23,7 @@ import static java.util.function.Function.identity; import com.android.bundle.Targeting.Abi.AbiAlias; -import com.android.tools.build.bundletool.utils.EnumMapper; +import com.android.tools.build.bundletool.model.utils.EnumMapper; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; 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 9735d31a..bafb175a 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 @@ -21,21 +21,23 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestFusingException.FusingMissingIncludeAttribute; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestVersionException.VersionCodeMissingException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttribute; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.BundleModule.ModuleType; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestFusingException.FusingMissingIncludeAttribute; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestVersionException.VersionCodeMissingException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; import java.util.stream.Stream; import javax.annotation.CheckReturnValue; @@ -45,7 +47,9 @@ * *

Implementations may be not thread safe. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class AndroidManifest { private static final Splitter COMMA_SPLITTER = Splitter.on(','); @@ -71,10 +75,17 @@ public abstract class AndroidManifest { public static final String MIN_SDK_VERSION_ATTRIBUTE_NAME = "minSdkVersion"; public static final String NAME_ATTRIBUTE_NAME = "name"; public static final String VALUE_ATTRIBUTE_NAME = "value"; + public static final String CODE_ATTRIBUTE_NAME = "code"; + public static final String EXCLUDE_ATTRIBUTE_NAME = "exclude"; + public static final String COUNTRY_ELEMENT_NAME = "country"; public static final String CONDITION_DEVICE_FEATURE_NAME = "device-feature"; public static final String CONDITION_MIN_SDK_VERSION_NAME = "min-sdk"; + public static final String CONDITION_USER_COUNTRIES_NAME = "user-countries"; public static final String SPLIT_NAME_ATTRIBUTE_NAME = "splitName"; + public static final String MODULE_TYPE_FEATURE_VALUE = "feature"; + public static final String MODULE_TYPE_ASSET_VALUE = "remote-asset"; + public static final int DEBUGGABLE_RESOURCE_ID = 0x0101000f; public static final int EXTRACT_NATIVE_LIBS_RESOURCE_ID = 0x10104ea; public static final int HAS_CODE_RESOURCE_ID = 0x101000c; @@ -250,6 +261,28 @@ public Optional getIsFeatureSplit() { .map(XmlProtoAttribute::getValueAsBoolean); } + private static ModuleType getModuleTypeFromAttributeValue(String value) { + switch (value) { + case MODULE_TYPE_FEATURE_VALUE: + return ModuleType.FEATURE_MODULE; + case MODULE_TYPE_ASSET_VALUE: + return ModuleType.ASSET_MODULE; + default: + throw ValidationException.builder() + .withMessage("Found invalid type attribute %s for element.", value) + .build(); + } + } + + public Optional getModuleType() { + Optional typeAttributeValue = + getManifestElement() + .getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "module") + .flatMap(module -> module.getAttribute(DISTRIBUTION_NAMESPACE_URI, "type")) + .map(XmlProtoAttribute::getValueAsString); + return typeAttributeValue.map(AndroidManifest::getModuleTypeFromAttributeValue); + } + public Optional getIsModuleIncludedInFusing() { return getManifestElement() .getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "module") diff --git a/src/main/java/com/android/tools/build/bundletool/model/ApkModifier.java b/src/main/java/com/android/tools/build/bundletool/model/ApkModifier.java index 3759bcc0..ce4a7a3d 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ApkModifier.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ApkModifier.java @@ -18,6 +18,7 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.VariantTargeting; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; import javax.annotation.CheckReturnValue; /** Modifier of APKs. */ @@ -32,7 +33,9 @@ public AndroidManifest modifyManifest(AndroidManifest manifest, ApkDescription a } /** Description of an APK generated by bundletool. */ + @Immutable @AutoValue + @AutoValue.CopyAnnotations public abstract static class ApkDescription { /** Builder for the {@link ApkDescription} class. */ @AutoValue.Builder diff --git a/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java b/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java index a92875c8..01df226c 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java +++ b/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java @@ -16,20 +16,24 @@ package com.android.tools.build.bundletool.model; -import static com.android.utils.ImmutableCollectors.toImmutableSet; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static java.util.function.Function.identity; import com.android.bundle.Config.BundleConfig; import com.android.bundle.Files.TargetedNativeDirectory; import com.android.bundle.Targeting.Abi; import com.android.bundle.Targeting.NativeDirectoryTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.ZipUtils; -import com.android.tools.build.bundletool.utils.files.BufferedIo; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; -import com.google.common.collect.ImmutableCollection; +import com.android.tools.build.bundletool.model.BundleModule.ModuleType; +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.ZipUtils; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; +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.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; @@ -39,6 +43,8 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -56,38 +62,60 @@ public class AppBundle { public static final String BUNDLE_CONFIG_FILE_NAME = "BundleConfig.pb"; - private final ImmutableMap modules; + private final ImmutableMap modulesByName; private final BundleConfig bundleConfig; private final BundleMetadata bundleMetadata; + private final ImmutableSet pinnedResourceIds; private AppBundle( - ImmutableMap modules, + ImmutableMap modulesByName, BundleConfig bundleConfig, - BundleMetadata bundleMetadata) { - this.modules = modules; + BundleMetadata bundleMetadata, + ImmutableSet pinnedResourceIds) { + this.modulesByName = modulesByName; this.bundleConfig = bundleConfig; this.bundleMetadata = bundleMetadata; + this.pinnedResourceIds = pinnedResourceIds; } /** Builds an {@link AppBundle} from an App Bundle on disk. */ public static AppBundle buildFromZip(ZipFile bundleFile) { BundleConfig bundleConfig = readBundleConfig(bundleFile); - return new AppBundle( + return buildFromModules( sanitize(extractModules(bundleFile, bundleConfig), bundleConfig), bundleConfig, readBundleMetadata(bundleFile)); } public static AppBundle buildFromModules( - ImmutableCollection modules, + ImmutableList modules, BundleConfig bundleConfig, BundleMetadata bundleMetadata) { + ImmutableSet pinnedResourceIds = + bundleConfig.getMasterResources().getResourceIdsList().stream() + .map(ResourceId::create) + .collect(toImmutableSet()); return new AppBundle( - Maps.uniqueIndex(modules, BundleModule::getName), bundleConfig, bundleMetadata); + Maps.uniqueIndex(modules, BundleModule::getName), + bundleConfig, + bundleMetadata, + pinnedResourceIds); + } + + public ImmutableMap getFeatureModules() { + return modulesByName.values().stream() + .filter(module -> module.getModuleType().equals(ModuleType.FEATURE_MODULE)) + .collect(toImmutableMap(BundleModule::getName, identity())); + } + + public ImmutableMap getAssetModules() { + return modulesByName.values().stream() + .filter(module -> module.getModuleType().equals(ModuleType.ASSET_MODULE)) + .collect(toImmutableMap(BundleModule::getName, identity())); } public ImmutableMap getModules() { - return modules; + return modulesByName; } public BundleModule getBaseModule() { @@ -95,7 +123,7 @@ public BundleModule getBaseModule() { } public BundleModule getModule(BundleModuleName moduleName) { - BundleModule module = modules.get(moduleName); + BundleModule module = modulesByName.get(moduleName); if (module == null) { throw CommandExecutionException.builder() .withMessage("Module '%s' not found.", moduleName) @@ -104,8 +132,16 @@ public BundleModule getModule(BundleModuleName moduleName) { return module; } + /** + * Predicate on resources that must remain in the master split regardless of their targeting + * configuration. + */ + public Predicate getMasterResourcesPredicate() { + return resource -> pinnedResourceIds.contains(resource.getResourceId()); + } + public boolean has32BitRenderscriptCode() { - return getModules().values().stream().anyMatch(BundleModule::hasRenderscript32Bitcode); + return getFeatureModules().values().stream().anyMatch(BundleModule::hasRenderscript32Bitcode); } public BundleConfig getBundleConfig() { @@ -125,7 +161,7 @@ public BundleMetadata getBundleMetadata() { *

Returns empty set if the App Bundle has no native code at all. */ public ImmutableSet getTargetedAbis() { - return modules.values().stream() + return getFeatureModules().values().stream() .map(BundleModule::getNativeConfig) .flatMap( nativeConfig -> { @@ -141,51 +177,65 @@ public ImmutableSet getTargetedAbis() { .collect(toImmutableSet()); } - private static Map extractModules( + /** + * Returns the {@link BundleModuleName} corresponding to the provided zip entry. If the zip entry + * does not belong to a module, a null {@link BundleModuleName} is returned. + */ + public static Optional extractModuleName(ZipEntry entry) { + ZipPath path = ZipPath.create(entry.getName()); + + // Ignoring bundle metadata files. + if (path.startsWith(METADATA_DIRECTORY)) { + return Optional.empty(); + } + + // Ignoring signature related files. + if (path.startsWith("META-INF")) { + return Optional.empty(); + } + + // Ignoring top-level files. + if (path.getNameCount() <= 1) { + return Optional.empty(); + } + + // Temporarily excluding .class files. + if (path.toString().endsWith(".class")) { + return Optional.empty(); + } + + return Optional.of(BundleModuleName.create(path.getName(0).toString())); + } + + private static ImmutableList extractModules( ZipFile bundleFile, BundleConfig bundleConfig) { Map moduleBuilders = new HashMap<>(); Enumeration entries = bundleFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); - ZipPath path = ZipPath.create(entry.getName()); - - // Ignoring bundle metadata files. - if (path.startsWith(METADATA_DIRECTORY)) { - continue; - } - - // Ignoring signature related files. - if (path.startsWith("META-INF")) { + Optional moduleName = extractModuleName(entry); + if (!moduleName.isPresent()) { continue; } - // Ignoring top-level files. - if (path.getNameCount() <= 1) { - continue; - } - - // Temporarily excluding .class files. - if (path.toString().endsWith(".class")) { - continue; - } - - BundleModuleName moduleName = BundleModuleName.create(path.getName(0).toString()); BundleModule.Builder moduleBuilder = - moduleBuilders.computeIfAbsent(moduleName, name -> BundleModule.builder().setName(name)); + moduleBuilders.computeIfAbsent( + moduleName.get(), + name -> BundleModule.builder().setName(name).setBundleConfig(bundleConfig)); try { moduleBuilder.addEntry(ModuleZipEntry.fromBundleZipEntry(entry, bundleFile)); } catch (IOException e) { throw ValidationException.builder() .withCause(e) .withMessage( - "Error processing zip entry '%s' of module '%s'.", entry.getName(), moduleName) + "Error processing zip entry '%s' of module '%s'.", + entry.getName(), moduleName.get()) .build(); } } - for (BundleModule.Builder value : moduleBuilders.values()) { - value.setBundleConfig(bundleConfig); - } - return Maps.transformValues(moduleBuilders, BundleModule.Builder::build); + return moduleBuilders.values().stream() + .map(module -> module.build()) + .collect(toImmutableList()); } private static BundleConfig readBundleConfig(ZipFile bundleFile) { @@ -216,22 +266,23 @@ private static BundleMetadata readBundleMetadata(ZipFile bundleFile) { ZipPath bundlePath = ZipPath.create(zipEntry.getName()); // Strip the top-level metadata directory. ZipPath metadataPath = bundlePath.subpath(1, bundlePath.getNameCount()); - metadata.addFile(metadataPath, () -> BufferedIo.inputStream(bundleFile, zipEntry)); + metadata.addFile(metadataPath, BufferedIo.inputStreamSupplier(bundleFile, zipEntry)); }); return metadata.build(); } @CheckReturnValue - private static ImmutableMap sanitize( - Map moduleMap, BundleConfig bundleConfig) { + private static ImmutableList sanitize( + ImmutableList modules, BundleConfig bundleConfig) { Version bundleVersion = BundleToolVersion.getVersionFromBundleConfig(bundleConfig); if (bundleVersion.isOlderThan(Version.of("0.3.1"))) { // This is a temporary fix to cope with inconsistent ABIs. - moduleMap = Maps.transformValues(moduleMap, new ModuleAbiSanitizer()::sanitize); + modules = modules.stream().map(new ModuleAbiSanitizer()::sanitize).collect(toImmutableList()); } // This is a temporary fix to work around a bug in gradle that creates a file named classes1.dex - moduleMap = Maps.transformValues(moduleMap, new ClassesDexNameSanitizer()::sanitize); + modules = + modules.stream().map(new ClassesDexNameSanitizer()::sanitize).collect(toImmutableList()); - return ImmutableMap.copyOf(moduleMap); + return modules; } } diff --git a/src/main/java/com/android/tools/build/bundletool/model/BundleMetadata.java b/src/main/java/com/android/tools/build/bundletool/model/BundleMetadata.java index d95484fb..28e11a88 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/BundleMetadata.java +++ b/src/main/java/com/android/tools/build/bundletool/model/BundleMetadata.java @@ -20,10 +20,13 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** Holder of the App Bundle metadata. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class BundleMetadata { /** Namespaced directory where files used by BundleTool are stored. */ 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 0d8ed14b..9b1c13d4 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 @@ -19,6 +19,9 @@ import static com.android.tools.build.bundletool.model.BundleModule.ModuleDeliveryType.ALWAYS_INITIAL_INSTALL; import static com.android.tools.build.bundletool.model.BundleModule.ModuleDeliveryType.CONDITIONAL_INITIAL_INSTALL; import static com.android.tools.build.bundletool.model.BundleModule.ModuleDeliveryType.NO_INITIAL_INSTALL; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static java.util.function.Function.identity; import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlNode; @@ -28,16 +31,18 @@ import com.android.bundle.Files.Assets; import com.android.bundle.Files.NativeLibraries; import com.android.bundle.Targeting.ModuleTargeting; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttribute; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.Collection; import java.util.Optional; import java.util.function.Predicate; @@ -49,7 +54,9 @@ *

The ZipEntries of the instances of this class refer only to regular files (and no * directories). */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class BundleModule { public static final String MANIFEST_FILENAME = "AndroidManifest.xml"; @@ -64,14 +71,6 @@ public abstract class BundleModule { /** The top-level directory of an App Bundle module that contains APEX image files. */ public static final ZipPath APEX_DIRECTORY = ZipPath.create("apex"); - public static final ZipPath ASSETS_PROTO_PATH = ZipPath.create("assets.pb"); - public static final ZipPath MANIFEST_PATH = MANIFEST_DIRECTORY.resolve(MANIFEST_FILENAME); - public static final ZipPath NATIVE_PROTO_PATH = ZipPath.create("native.pb"); - public static final ZipPath RESOURCES_PROTO_PATH = ZipPath.create("resources.pb"); - - /** The top-level file of an App Bundle module that contains APEX targeting configuration. */ - public static final ZipPath APEX_PROTO_PATH = ZipPath.create("apex.pb"); - /** The file of an App Bundle module that contains the APEX manifest. */ public static final ZipPath APEX_MANIFEST_PATH = ZipPath.create("root/apex_manifest.json"); @@ -85,7 +84,13 @@ public enum ModuleDeliveryType { ALWAYS_INITIAL_INSTALL, CONDITIONAL_INITIAL_INSTALL, NO_INITIAL_INSTALL - }; + } + + /** Describes the content type of the module. */ + public enum ModuleType { + FEATURE_MODULE, + ASSET_MODULE + } /** The version of Bundletool that built this module, taken from BundleConfig. */ public abstract BundleConfig getBundleConfig(); @@ -153,6 +158,12 @@ public ModuleDeliveryType getDeliveryType() { return ALWAYS_INITIAL_INSTALL; } + public ModuleType getModuleType() { + // If the module type is not defined in the manifest, default to feature module for backwards + // compatibility. + return getAndroidManifest().getModuleType().orElse(ModuleType.FEATURE_MODULE); + } + public boolean isIncludedInFusing() { // The following should never throw if the module/bundle has been validated. return isBaseModule() || getAndroidManifest().getIsModuleIncludedInFusing().get(); @@ -220,7 +231,7 @@ public static Builder builder() { return new AutoValue_BundleModule.Builder(); } - abstract Builder toBuilder(); + public abstract Builder toBuilder(); /** Builder for BundleModule. */ @AutoValue.Builder @@ -229,21 +240,37 @@ public abstract static class Builder { public abstract Builder setBundleConfig(BundleConfig value); - abstract BundleConfig getBundleConfig(); + public abstract Builder setResourceTable(ResourceTable resourceTable); - abstract ImmutableMap.Builder entryMapBuilder(); + public abstract Builder setAndroidManifestProto(XmlNode manifestProto); - abstract Builder setEntryMap(ImmutableMap entryMap); + public abstract Builder setAssetsConfig(Assets assetsConfig); - abstract Builder setAndroidManifestProto(XmlNode manifestProto); + public abstract Builder setNativeConfig(NativeLibraries nativeConfig); - abstract Builder setResourceTable(ResourceTable resourceTable); + public abstract Builder setApexConfig(ApexImages apexConfig); - abstract Builder setAssetsConfig(Assets assetsConfig); + abstract ImmutableMap.Builder entryMapBuilder(); - abstract Builder setNativeConfig(NativeLibraries nativeConfig); + abstract Builder setEntryMap(ImmutableMap entryMap); - abstract Builder setApexConfig(ApexImages apexConfig); + /** + * Convenience method to set all entries at once. + * + *

Note: this method does not accept special entries such as manifest or resource table. + * Thus, prefer using the {@link #addEntry(ModuleEntry)} method when possible, since it also + * accepts the special files. + */ + public Builder setRawEntries(Collection entries) { + entries.forEach( + entry -> + checkArgument( + !SpecialModuleEntry.getSpecialEntry(entry.getPath()).isPresent(), + "Cannot add special entry '%s' using method setRawEntries.", + entry.getPath())); + setEntryMap(entries.stream().collect(toImmutableMap(ModuleEntry::getPath, identity()))); + return this; + } /** @see #addEntry(ModuleEntry) */ public Builder addEntries(Collection entries) throws IOException { @@ -262,25 +289,11 @@ public Builder addEntries(Collection entries) throws IOException { * @throws IOException when the entry cannot be read or has invalid contents */ public Builder addEntry(ModuleEntry moduleEntry) throws IOException { - if (moduleEntry.getPath().equals(MANIFEST_PATH)) { - try (InputStream inputStream = moduleEntry.getContent()) { - setAndroidManifestProto(XmlNode.parseFrom(inputStream)); - } - } else if (moduleEntry.getPath().equals(RESOURCES_PROTO_PATH)) { + Optional specialEntry = + SpecialModuleEntry.getSpecialEntry(moduleEntry.getPath()); + if (specialEntry.isPresent()) { try (InputStream inputStream = moduleEntry.getContent()) { - setResourceTable(ResourceTable.parseFrom(inputStream)); - } - } else if (moduleEntry.getPath().equals(ASSETS_PROTO_PATH)) { - try (InputStream inputStream = moduleEntry.getContent()) { - setAssetsConfig(Assets.parseFrom(inputStream)); - } - } else if (moduleEntry.getPath().equals(NATIVE_PROTO_PATH)) { - try (InputStream inputStream = moduleEntry.getContent()) { - setNativeConfig(NativeLibraries.parseFrom(inputStream)); - } - } else if (moduleEntry.getPath().equals(APEX_PROTO_PATH)) { - try (InputStream inputStream = moduleEntry.getContent()) { - setApexConfig(ApexImages.parseFrom(inputStream)); + specialEntry.get().addToModule(this, inputStream); } } else if (!moduleEntry.isDirectory()) { entryMapBuilder().put(moduleEntry.getPath(), moduleEntry); @@ -291,4 +304,67 @@ public Builder addEntry(ModuleEntry moduleEntry) throws IOException { public abstract BundleModule build(); } + + /** + * A special entry in a module of the Android App Bundle. + * + *

An entry is considered special when it's read by bundletool. + */ + public enum SpecialModuleEntry { + ANDROID_MANIFEST("manifest/AndroidManifest.xml") { + @Override + void addToModule(BundleModule.Builder module, InputStream inputStream) throws IOException { + module.setAndroidManifestProto(XmlNode.parseFrom(inputStream)); + } + }, + RESOURCE_TABLE("resources.pb") { + @Override + void addToModule(BundleModule.Builder module, InputStream inputStream) throws IOException { + module.setResourceTable(ResourceTable.parseFrom(inputStream)); + } + }, + ASSETS_TABLE("assets.pb") { + @Override + void addToModule(BundleModule.Builder module, InputStream inputStream) throws IOException { + module.setAssetsConfig(Assets.parseFrom(inputStream)); + } + }, + NATIVE_LIBS_TABLE("native.pb") { + @Override + void addToModule(BundleModule.Builder module, InputStream inputStream) throws IOException { + module.setNativeConfig(NativeLibraries.parseFrom(inputStream)); + } + }, + APEX_TABLE("apex.pb") { + @Override + void addToModule(BundleModule.Builder module, InputStream inputStream) throws IOException { + module.setApexConfig(ApexImages.parseFrom(inputStream)); + } + }; + + private static final ImmutableMap SPECIAL_ENTRY_BY_PATH = + Arrays.stream(SpecialModuleEntry.values()) + .collect(toImmutableMap(SpecialModuleEntry::getPath, identity())); + + abstract void addToModule(BundleModule.Builder module, InputStream inputStream) + throws IOException; + + /** + * Returns the {@link SpecialModuleEntry} instance associated with the given path, or an empty + * Optional if the path is not a special entry. + */ + public static Optional getSpecialEntry(ZipPath entryPath) { + return Optional.ofNullable(SPECIAL_ENTRY_BY_PATH.get(entryPath)); + } + + private final ZipPath entryPath; + + private SpecialModuleEntry(String entryPath) { + this.entryPath = ZipPath.create(entryPath); + } + + public ZipPath getPath() { + return entryPath; + } + } } diff --git a/src/main/java/com/android/tools/build/bundletool/model/BundleModuleName.java b/src/main/java/com/android/tools/build/bundletool/model/BundleModuleName.java index 1b9858f2..482c302b 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/BundleModuleName.java +++ b/src/main/java/com/android/tools/build/bundletool/model/BundleModuleName.java @@ -16,8 +16,9 @@ package com.android.tools.build.bundletool.model; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; import java.util.Comparator; import java.util.regex.Pattern; @@ -26,7 +27,9 @@ * *

This class ensures that module names meet the required naming format. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class BundleModuleName implements Comparable { public static final String BASE_MODULE_NAME = "base"; diff --git a/src/main/java/com/android/tools/build/bundletool/model/ClassesDexNameSanitizer.java b/src/main/java/com/android/tools/build/bundletool/model/ClassesDexNameSanitizer.java index f460408f..502084b6 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ClassesDexNameSanitizer.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ClassesDexNameSanitizer.java @@ -22,9 +22,10 @@ import static java.util.stream.Collectors.partitioningBy; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.MustBeClosed; import java.io.InputStream; import java.util.Map; -import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -62,9 +63,8 @@ public BundleModule sanitize(BundleModule module) { ImmutableList.builder() .addAll(nonDexEntries) .addAll( - dexEntries - .stream() - .map(entry -> new RenamedModuleEntry(entry, this::incrementClassesDexNumber)) + dexEntries.stream() + .map(entry -> new RenamedDexEntry(entry)) .collect(toImmutableList())) .build(); @@ -74,39 +74,17 @@ public BundleModule sanitize(BundleModule module) { .build(); } - /** - * Increment the suffix of multidex files. - * - *

-   * dex/classes.dex -> dex/classes.dex
-   * dex/classes1.dex -> dex/classes2.dex
-   * dex/classes2.dex -> dex/classes3.dex
-   * 
- */ - private ZipPath incrementClassesDexNumber(ZipPath entryPath) { - String fileName = entryPath.toString(); - - Matcher matcher = CLASSES_DEX_REGEX_PATTERN.matcher(fileName); - checkState(matcher.matches()); - - String num = matcher.group(1); - if (num.isEmpty()) { - return entryPath; // dex/classes.dex - } - return ZipPath.create("dex/classes" + (Integer.parseInt(num) + 1) + ".dex"); - } - /** A ModuleEntry that has the same content as an other ModuleEntry but uses a different name. */ - private static class RenamedModuleEntry implements ModuleEntry { + @Immutable + private static class RenamedDexEntry implements ModuleEntry { private final ModuleEntry moduleEntry; - private final Function renameFunction; - RenamedModuleEntry(ModuleEntry moduleEntry, Function renameFunction) { + RenamedDexEntry(ModuleEntry moduleEntry) { this.moduleEntry = moduleEntry; - this.renameFunction = renameFunction; } + @MustBeClosed @Override public InputStream getContent() { return moduleEntry.getContent(); @@ -114,7 +92,7 @@ public InputStream getContent() { @Override public ZipPath getPath() { - return renameFunction.apply(moduleEntry.getPath()); + return incrementClassesDexNumber(moduleEntry.getPath()); } @Override @@ -144,5 +122,27 @@ public boolean equals(Object obj) { public int hashCode() { return moduleEntry.hashCode(); } + + /** + * Increment the suffix of multidex files. + * + *
+     * dex/classes.dex -- dex/classes.dex
+     * dex/classes1.dex -- dex/classes2.dex
+     * dex/classes2.dex -- dex/classes3.dex
+     * 
+ */ + private ZipPath incrementClassesDexNumber(ZipPath entryPath) { + String fileName = entryPath.toString(); + + Matcher matcher = CLASSES_DEX_REGEX_PATTERN.matcher(fileName); + checkState(matcher.matches()); + + String num = matcher.group(1); + if (num.isEmpty()) { + return entryPath; // dex/classes.dex + } + return ZipPath.create("dex/classes" + (Integer.parseInt(num) + 1) + ".dex"); + } } } diff --git a/src/main/java/com/android/tools/build/bundletool/model/ConfigurationSizes.java b/src/main/java/com/android/tools/build/bundletool/model/ConfigurationSizes.java index 5e66c5f1..4726eaa4 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ConfigurationSizes.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ConfigurationSizes.java @@ -17,9 +17,12 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; /** Holder of min and max size maps for each {@link SizeConfiguration}. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ConfigurationSizes { public abstract ImmutableMap getMinSizeConfigurationMap(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/DeviceFeatureCondition.java b/src/main/java/com/android/tools/build/bundletool/model/DeviceFeatureCondition.java index 6d709e87..26572141 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/DeviceFeatureCondition.java +++ b/src/main/java/com/android/tools/build/bundletool/model/DeviceFeatureCondition.java @@ -17,10 +17,13 @@ package com.android.tools.build.bundletool.model; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** A {@link BundleModule} condition describing a certain device feature. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class DeviceFeatureCondition { public abstract String getFeatureName(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/FileSystemModuleEntry.java b/src/main/java/com/android/tools/build/bundletool/model/FileSystemModuleEntry.java index a21f20b8..b71167a2 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/FileSystemModuleEntry.java +++ b/src/main/java/com/android/tools/build/bundletool/model/FileSystemModuleEntry.java @@ -18,8 +18,10 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.MustBeClosed; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -34,7 +36,9 @@ * *

Always reflects current content of the underlying file. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class FileSystemModuleEntry implements ModuleEntry { @Override @@ -48,6 +52,7 @@ public abstract class FileSystemModuleEntry implements ModuleEntry { abstract Path getFileSystemPath(); + @MustBeClosed @Override public InputStream getContent() { try { diff --git a/src/main/java/com/android/tools/build/bundletool/model/GeneratedApks.java b/src/main/java/com/android/tools/build/bundletool/model/GeneratedApks.java index af7c3626..2ba966c1 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/GeneratedApks.java +++ b/src/main/java/com/android/tools/build/bundletool/model/GeneratedApks.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.model; -import static com.android.tools.build.bundletool.utils.CollectorUtils.groupingBySortedKeys; +import static com.android.tools.build.bundletool.model.utils.CollectorUtils.groupingBySortedKeys; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.stream.Collectors.groupingBy; @@ -24,13 +24,16 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; +import com.google.errorprone.annotations.Immutable; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Stream; /** Represents generated APKs and groups them into standalone, split, and instant ones. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class GeneratedApks { public abstract ImmutableList getInstantApks(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/GeneratedAssetSlices.java b/src/main/java/com/android/tools/build/bundletool/model/GeneratedAssetSlices.java new file mode 100755 index 00000000..3910ae8c --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/model/GeneratedAssetSlices.java @@ -0,0 +1,46 @@ +/* + * 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; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; + +/** Represents a collection of generated Asset Slices. */ +@Immutable +@AutoValue +@AutoValue.CopyAnnotations +public abstract class GeneratedAssetSlices { + + public abstract ImmutableList getAssetSlices(); + + public int size() { + return getAssetSlices().size(); + } + + /** Creates a GeneratedAssetSlices instance from a list of module splits. */ + public static GeneratedAssetSlices fromModuleSplits(ImmutableList moduleSplits) { + ImmutableList assetSlices = + moduleSplits.stream() + .filter(split -> split.getSplitType().equals(SplitType.ASSET_SLICE)) + .collect(toImmutableList()); + return new AutoValue_GeneratedAssetSlices(assetSlices); + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/model/GetSizeRequest.java b/src/main/java/com/android/tools/build/bundletool/model/GetSizeRequest.java index 6af10cd5..7567ce44 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/GetSizeRequest.java +++ b/src/main/java/com/android/tools/build/bundletool/model/GetSizeRequest.java @@ -17,13 +17,21 @@ package com.android.tools.build.bundletool.model; import com.android.bundle.Devices.DeviceSpec; -import com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension; import com.google.common.collect.ImmutableSet; import java.util.Optional; /** Request to compute the size of APKs for a given device spec. */ public interface GetSizeRequest { + /** Dimensions to expand the sizes against. */ + public enum Dimension { + SDK, + ABI, + SCREEN_DENSITY, + LANGUAGE, + ALL + } + DeviceSpec getDeviceSpec(); Optional> getModules(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/InMemoryModuleEntry.java b/src/main/java/com/android/tools/build/bundletool/model/InMemoryModuleEntry.java index ed9ea7cd..a0a28590 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/InMemoryModuleEntry.java +++ b/src/main/java/com/android/tools/build/bundletool/model/InMemoryModuleEntry.java @@ -16,12 +16,15 @@ package com.android.tools.build.bundletool.model; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; import com.google.protobuf.ByteString; import java.io.ByteArrayInputStream; import java.io.InputStream; /** In-memory implementation of a {@link ModuleEntry}. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class InMemoryModuleEntry implements ModuleEntry { @Override diff --git a/src/main/java/com/android/tools/build/bundletool/model/InputStreamSupplier.java b/src/main/java/com/android/tools/build/bundletool/model/InputStreamSupplier.java index 0ad707c9..943e05be 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/InputStreamSupplier.java +++ b/src/main/java/com/android/tools/build/bundletool/model/InputStreamSupplier.java @@ -16,6 +16,8 @@ package com.android.tools.build.bundletool.model; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.MustBeClosed; import java.io.IOException; import java.io.InputStream; @@ -24,7 +26,11 @@ * *

To be used to access various types of data, such as files on disk, zip file entries or raw * byte data. + * + *

All subclasses must be immutable, and return the same {@link InputStream} at each invocation. */ +@Immutable public interface InputStreamSupplier { + @MustBeClosed InputStream get() throws IOException; } 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 3fc44129..4f2aa962 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 @@ -16,26 +16,33 @@ package com.android.tools.build.bundletool.model; +import static com.android.tools.build.bundletool.model.AndroidManifest.CODE_ATTRIBUTE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.CONDITION_DEVICE_FEATURE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.CONDITION_MIN_SDK_VERSION_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.CONDITION_USER_COUNTRIES_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.COUNTRY_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.DISTRIBUTION_NAMESPACE_URI; +import static com.android.tools.build.bundletool.model.AndroidManifest.EXCLUDE_ATTRIBUTE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_ATTRIBUTE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.VALUE_ATTRIBUTE_NAME; import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttribute; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** Parses and provides business logic utilities for element. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ManifestDeliveryElement { private static final String VERSION_ATTRIBUTE_NAME = "version"; @@ -75,8 +82,9 @@ public boolean hasInstallTimeElement() { /** * Returns all module conditions. * - *

We support and conditions today. Any other - * conditions types are not supported and will result in {@link ValidationException}. + *

We support , and + * conditions today. Any other conditions types are not supported and will result in {@link + * ValidationException}. */ @Memoized public ModuleConditions getModuleConditions() { @@ -99,6 +107,9 @@ public ModuleConditions getModuleConditions() { case CONDITION_MIN_SDK_VERSION_NAME: moduleConditions.setMinSdkVersion(parseMinSdkVersionCondition(conditionElement)); break; + case CONDITION_USER_COUNTRIES_NAME: + moduleConditions.setUserCountriesCondition(parseUserCountriesCondition(conditionElement)); + break; default: throw new ValidationException( String.format("Unrecognized module condition: '%s'", conditionElement.getName())); @@ -107,6 +118,38 @@ public ModuleConditions getModuleConditions() { return moduleConditions.build(); } + private UserCountriesCondition parseUserCountriesCondition(XmlProtoElement conditionElement) { + ImmutableList.Builder countryCodes = ImmutableList.builder(); + for (XmlProtoElement countryElement : + conditionElement.getChildrenElements().collect(toImmutableList())) { + if (!countryElement.getName().equals(COUNTRY_ELEMENT_NAME)) { + throw ValidationException.builder() + .withMessage( + "Expected only elements inside , but found %s", + printElement(conditionElement)) + .build(); + } + countryCodes.add( + countryElement + .getAttribute(DISTRIBUTION_NAMESPACE_URI, CODE_ATTRIBUTE_NAME) + .map(XmlProtoAttribute::getValueAsString) + .map(String::toUpperCase) + .orElseThrow( + () -> + ValidationException.builder() + .withMessage( + " element is expected to have 'dist:code' attribute " + + "but found none.") + .build())); + } + boolean exclude = + conditionElement + .getAttribute(DISTRIBUTION_NAMESPACE_URI, EXCLUDE_ATTRIBUTE_NAME) + .map(XmlProtoAttribute::getValueAsBoolean) + .orElse(false); + return UserCountriesCondition.create(countryCodes.build(), exclude); + } + private static void validateDeliveryElement(XmlProtoElement deliveryElement) { validateDeliveryElementChildren(deliveryElement); validateInstallTimeElement( diff --git a/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java b/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java index 1a4df955..f9e5cabc 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java @@ -44,16 +44,17 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.USES_SDK_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.VALUE_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.VERSION_CODE_RESOURCE_ID; -import static com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder.createAndroidAttribute; +import static com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder.createAndroidAttribute; import static com.google.common.collect.MoreCollectors.toOptional; import static java.util.stream.Collectors.joining; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNodeBuilder; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNodeBuilder; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.util.Optional; import javax.annotation.CheckReturnValue; @@ -227,6 +228,29 @@ public ManifestEditor removeSplitName() { return this; } + /** + * Removes the activities, services, and providers that contain an unknown {@code splitName}. + * + *

This is useful for converting between install and instant splits. + */ + public ManifestEditor removeUnknownSplitComponents(ImmutableSet allModuleNames) { + Optional applicationElement = + manifestElement.getOptionalChildElement(APPLICATION_ELEMENT_NAME); + if (!applicationElement.isPresent()) { + return this; + } + applicationElement + .get() + .removeChildrenElementsIf( + el -> + el.isElement() + && el.getElement() + .getAndroidAttribute(SPLIT_NAME_RESOURCE_ID) + .filter(attr -> !allModuleNames.contains(attr.getValueAsString())) + .isPresent()); + return this; + } + /** Generates the modified manifest. */ @CheckReturnValue public AndroidManifest save() { diff --git a/src/main/java/com/android/tools/build/bundletool/model/ManifestMutator.java b/src/main/java/com/android/tools/build/bundletool/model/ManifestMutator.java index 3b30a617..d76501dc 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ManifestMutator.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ManifestMutator.java @@ -16,9 +16,11 @@ package com.android.tools.build.bundletool.model; +import com.google.errorprone.annotations.Immutable; import java.util.function.Consumer; /** Represents a mutation to manifest, which can then be applied to manifest for editing it. */ +@Immutable public interface ManifestMutator extends Consumer { static ManifestMutator withExtractNativeLibs(boolean value) { diff --git a/src/main/java/com/android/tools/build/bundletool/model/ModuleAbiSanitizer.java b/src/main/java/com/android/tools/build/bundletool/model/ModuleAbiSanitizer.java index f5319ae1..0d603ae2 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ModuleAbiSanitizer.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ModuleAbiSanitizer.java @@ -23,7 +23,6 @@ import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap; import com.android.bundle.Files.NativeLibraries; -import com.android.tools.build.bundletool.model.BundleModule.Builder; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; @@ -84,9 +83,7 @@ public BundleModule sanitize(BundleModule module) { */ private static ImmutableMultimap indexLibFilesByAbiDir( BundleModule module) { - return module - .getEntries() - .stream() + return module.getEntries().stream() .filter(entry -> entry.getPath().startsWith(LIB_DIRECTORY)) .collect( toImmutableSetMultimap(entry -> entry.getPath().subpath(0, 2), Function.identity())); @@ -107,7 +104,7 @@ private static BundleModule sanitizedModule( BundleModule module, ImmutableMultimap libFilesByAbiDirToKeep) { // Construct new module by making minimal modifications to the existing module. - Builder newModule = module.toBuilder(); + BundleModule.Builder newModule = module.toBuilder(); if (module.getNativeConfig().isPresent()) { NativeLibraries newNativeConfig = @@ -116,9 +113,7 @@ private static BundleModule sanitizedModule( } newModule.setEntryMap( - module - .getEntries() - .stream() + module.getEntries().stream() .filter( entry -> !entry.getPath().startsWith(LIB_DIRECTORY) @@ -139,9 +134,7 @@ private static NativeLibraries filterNativeTargeting( .toBuilder() .clearDirectory() .addAllDirectory( - nativeLibraries - .getDirectoryList() - .stream() + nativeLibraries.getDirectoryList().stream() .filter(targetedDirectory -> preservedAbiDirs.contains(targetedDirectory.getPath())) .collect(toImmutableList())) .build(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/ModuleConditions.java b/src/main/java/com/android/tools/build/bundletool/model/ModuleConditions.java index 95eda91b..ed7fb2ff 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ModuleConditions.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ModuleConditions.java @@ -16,24 +16,30 @@ package com.android.tools.build.bundletool.model; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionFrom; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionTargeting; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.sdkVersionFrom; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.sdkVersionTargeting; import com.android.bundle.Targeting.DeviceFeature; import com.android.bundle.Targeting.DeviceFeatureTargeting; import com.android.bundle.Targeting.ModuleTargeting; +import com.android.bundle.Targeting.UserCountriesTargeting; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** Encapsulates all {@link BundleModule} conditions. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ModuleConditions { public abstract ImmutableList getDeviceFeatureConditions(); public abstract Optional getMinSdkVersion(); + public abstract Optional getUserCountriesCondition(); + public boolean isEmpty() { return toTargeting().equals(ModuleTargeting.getDefaultInstance()); } @@ -58,6 +64,15 @@ public ModuleTargeting toTargeting() { sdkVersionTargeting(sdkVersionFrom(getMinSdkVersion().get()))); } + if (getUserCountriesCondition().isPresent()) { + UserCountriesCondition condition = getUserCountriesCondition().get(); + moduleTargeting.setUserCountriesTargeting( + UserCountriesTargeting.newBuilder() + .addAllCountryCodes(condition.getCountries()) + .setExclude(condition.getExclude()) + .build()); + } + return moduleTargeting.build(); } @@ -74,6 +89,9 @@ public Builder addDeviceFeatureCondition(DeviceFeatureCondition deviceFeatureCon public abstract Builder setMinSdkVersion(int minSdkVersion); + public abstract Builder setUserCountriesCondition( + UserCountriesCondition userCountriesCondition); + public abstract ModuleConditions build(); } } diff --git a/src/main/java/com/android/tools/build/bundletool/model/ModuleEntry.java b/src/main/java/com/android/tools/build/bundletool/model/ModuleEntry.java index a5b5db05..22cf867f 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ModuleEntry.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ModuleEntry.java @@ -15,12 +15,20 @@ */ package com.android.tools.build.bundletool.model; -import com.android.tools.build.bundletool.utils.files.FileUtils; +import com.android.tools.build.bundletool.model.utils.files.FileUtils; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.MustBeClosed; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; -/** Represents an entry in a an App Bundle's module. */ +/** + * Represents an entry in a an App Bundle's module. + * + *

All subclasses should be immutable, and we assume that they are as long as the data source + * backing this entry remains unchanged. + */ +@Immutable public interface ModuleEntry { /** @@ -29,8 +37,14 @@ public interface ModuleEntry { *

Each implementation should strongly consider returning {@link java.io.BufferedInputStream} * for efficiency. */ + @MustBeClosed InputStream getContent(); + @SuppressWarnings("MustBeClosedChecker") // InputStreamSupplier is annotated with @MustBeClosed + default InputStreamSupplier getContentSupplier() { + return () -> getContent(); + } + /** Path of the entry inside the module. */ ZipPath getPath(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/ModuleSplit.java b/src/main/java/com/android/tools/build/bundletool/model/ModuleSplit.java index a88e1354..5a76eeac 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ModuleSplit.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ModuleSplit.java @@ -22,8 +22,9 @@ import static com.android.tools.build.bundletool.model.BundleModule.LIB_DIRECTORY; import static com.android.tools.build.bundletool.model.BundleModule.RESOURCES_DIRECTORY; import static com.android.tools.build.bundletool.model.BundleModule.ROOT_DIRECTORY; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.SCREEN_DENSITY_TO_PROTO_VALUE_MAP; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.lPlusVariantTargeting; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.SCREEN_DENSITY_TO_PROTO_VALUE_MAP; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.lPlusVariantTargeting; +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.MoreCollectors.toOptional; @@ -44,13 +45,15 @@ import com.android.bundle.Targeting.TextureCompressionFormatTargeting; import com.android.bundle.Targeting.VariantTargeting; import com.android.bundle.Targeting.VulkanVersion; -import com.android.tools.build.bundletool.utils.ResourcesUtils; +import com.android.tools.build.bundletool.model.BundleModule.ModuleType; +import com.android.tools.build.bundletool.model.utils.ResourcesUtils; import com.google.auto.value.AutoValue; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; import java.util.List; import java.util.Optional; import java.util.StringJoiner; @@ -58,7 +61,9 @@ import javax.annotation.CheckReturnValue; /** A module split is a subset of a bundle module. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ModuleSplit { private static final Joiner MULTI_ABI_SUFFIX_JOINER = Joiner.on('.'); @@ -68,7 +73,8 @@ public enum SplitType { STANDALONE, SYSTEM, SPLIT, - INSTANT + INSTANT, + ASSET_SLICE, } /** Returns the targeting of the APK represented by this instance. */ @@ -224,6 +230,12 @@ public ModuleSplit removeSplitName() { return toBuilder().setAndroidManifest(apkManifest).build(); } + public ModuleSplit removeUnknownSplitComponents(ImmutableSet knownSplits) { + AndroidManifest apkManifest = + getAndroidManifest().toEditor().removeUnknownSplitComponents(knownSplits).save(); + return toBuilder().setAndroidManifest(apkManifest).build(); + } + /** Writes the final manifest that reflects the Split ID. */ @CheckReturnValue public ModuleSplit writeSplitIdInManifest(String resolvedSplitIdSuffix) { @@ -286,7 +298,7 @@ public static ModuleSplit forModule(BundleModule bundleModule) { */ public static ModuleSplit forModule( BundleModule bundleModule, VariantTargeting variantTargeting) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, Predicates.alwaysTrue(), /* setResourceTable= */ true, variantTargeting); } @@ -304,7 +316,7 @@ public static ModuleSplit forResources(BundleModule bundleModule) { */ public static ModuleSplit forResources( BundleModule bundleModule, VariantTargeting variantTargeting) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, entry -> entry.getPath().startsWith(RESOURCES_DIRECTORY), /* setResourceTable= */ true, @@ -325,7 +337,7 @@ public static ModuleSplit forAssets(BundleModule bundleModule) { */ public static ModuleSplit forAssets( BundleModule bundleModule, VariantTargeting variantTargeting) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, entry -> entry.getPath().startsWith(ASSETS_DIRECTORY), /* setResourceTable= */ false, @@ -346,7 +358,7 @@ public static ModuleSplit forNativeLibraries(BundleModule bundleModule) { */ public static ModuleSplit forNativeLibraries( BundleModule bundleModule, VariantTargeting variantTargeting) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, entry -> entry.getPath().startsWith(LIB_DIRECTORY), /* setResourceTable= */ false, @@ -366,7 +378,7 @@ public static ModuleSplit forDex(BundleModule bundleModule) { * variant targeting. */ public static ModuleSplit forDex(BundleModule bundleModule, VariantTargeting variantTargeting) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, entry -> entry.getPath().startsWith(DEX_DIRECTORY), /* setResourceTable= */ false, @@ -378,7 +390,7 @@ public static ModuleSplit forRoot(BundleModule bundleModule) { } public static ModuleSplit forRoot(BundleModule bundleModule, VariantTargeting variantTargeting) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, entry -> entry.getPath().startsWith(ROOT_DIRECTORY), /* setResourceTable= */ false, @@ -390,7 +402,7 @@ public static ModuleSplit forRoot(BundleModule bundleModule, VariantTargeting va * default L+ variant targeting. */ public static ModuleSplit forApex(BundleModule bundleModule) { - return fromBundleModule( + return fromFeatureBundleModule( bundleModule, entry -> entry.getPath().startsWith(APEX_DIRECTORY), /* setResourceTable= */ false, @@ -403,11 +415,15 @@ public static ModuleSplit forApex(BundleModule bundleModule) { * *

The created instance is not standalone thus its variant targets L+ devices initially. */ - private static ModuleSplit fromBundleModule( + private static ModuleSplit fromFeatureBundleModule( BundleModule bundleModule, Predicate entriesPredicate, boolean setResourceTable, VariantTargeting variantTargeting) { + checkArgument( + bundleModule.getModuleType().equals(ModuleType.FEATURE_MODULE), + "Expected a Feature Module, got %s", + bundleModule.getModuleType()); ModuleSplit.Builder splitBuilder = builder() .setModuleName(bundleModule.getName()) @@ -431,6 +447,26 @@ private static ModuleSplit fromBundleModule( return splitBuilder.build(); } + public static ModuleSplit fromAssetBundleModule(BundleModule bundleModule) { + checkArgument( + bundleModule.getModuleType().equals(ModuleType.ASSET_MODULE), + "Expected an Asset Module, got %s", + bundleModule.getModuleType()); + ModuleSplit.Builder splitBuilder = + ModuleSplit.builder() + .setModuleName(bundleModule.getName()) + .setEntries(bundleModule.getEntries().asList()) + .setAndroidManifest(bundleModule.getAndroidManifest()) + .setMasterSplit(true) + .setSplitType(SplitType.ASSET_SLICE) + .setApkTargeting(ApkTargeting.getDefaultInstance()) + .setVariantTargeting(VariantTargeting.getDefaultInstance()); + + bundleModule.getAssetsConfig().ifPresent(splitBuilder::setAssetsConfig); + + return splitBuilder.build(); + } + /** Returns all {@link ModuleEntry} that have a relative module path under a given path. */ public Stream findEntriesUnderPath(String path) { return getEntries().stream().filter(entry -> entry.getPath().startsWith(path)); diff --git a/src/main/java/com/android/tools/build/bundletool/model/ModuleZipEntry.java b/src/main/java/com/android/tools/build/bundletool/model/ModuleZipEntry.java index 89104c5c..071f7316 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ModuleZipEntry.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ModuleZipEntry.java @@ -17,8 +17,10 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.MustBeClosed; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -31,7 +33,12 @@ *

It is the responsibility of the caller to ensure that the referenced {@link ZipFile} stays * opened for the lifetime of the {@link ModuleZipEntry} instance. */ +// This class is only immutable as long as the underlying zip file doesn't change, but if the zip +// file changes, nothing we're doing in bundletool makes sense. +@SuppressWarnings("Immutable") +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ModuleZipEntry implements ModuleEntry { abstract ZipEntry getZipEntry(); @@ -58,6 +65,7 @@ public abstract class ModuleZipEntry implements ModuleEntry { @Override public abstract boolean shouldCompress(); + @MustBeClosed @Override public InputStream getContent() { try { diff --git a/src/main/java/com/android/tools/build/bundletool/model/Password.java b/src/main/java/com/android/tools/build/bundletool/model/Password.java new file mode 100755 index 00000000..a56a2f3b --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/model/Password.java @@ -0,0 +1,41 @@ +/* + * 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; + +import com.google.common.annotations.VisibleForTesting; +import java.security.KeyStore.PasswordProtection; +import java.util.function.Supplier; + +/** Wrapper around a password. */ +public final class Password { + + private final Supplier passwordSupplier; + + public Password(Supplier passwordSupplier) { + this.passwordSupplier = passwordSupplier; + } + + @VisibleForTesting + public static Password createForTest(String password) { + return new Password(() -> new PasswordProtection(password.toCharArray())); + } + + /** Special note: It's the responsibility of the caller to destroy the password once used. */ + public final PasswordProtection getValue() { + return passwordSupplier.get(); + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/model/ResourceId.java b/src/main/java/com/android/tools/build/bundletool/model/ResourceId.java index d677911d..e2285aa0 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ResourceId.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ResourceId.java @@ -22,15 +22,22 @@ import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.Immutable; /** Represents a resource id in an APK's resource table. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ResourceId { public static final int MAX_ENTRY_ID = 0xffff; public static final int MAX_TYPE_ID = 0xff; public static final int MAX_PACKAGE_ID = 0xff; + private static final int ENTRY_ID_MASK = 0x0000ffff; + private static final int TYPE_ID_MASK = 0x00ff0000; + private static final int PACKAGE_ID_MASK = 0xff000000; + private static final int PACKAGE_SHIFT = 24; private static final int TYPE_SHIFT = 16; @@ -48,6 +55,14 @@ public static ResourceId create(PackageOrBuilder pkg, TypeOrBuilder type, EntryO .build(); } + public static ResourceId create(int fullResourceId) { + return builder() + .setPackageId((fullResourceId & PACKAGE_ID_MASK) >>> PACKAGE_SHIFT) + .setTypeId((fullResourceId & TYPE_ID_MASK) >>> TYPE_SHIFT) + .setEntryId(fullResourceId & ENTRY_ID_MASK) + .build(); + } + public static Builder builder() { return new AutoValue_ResourceId.Builder(); } @@ -67,13 +82,16 @@ public ResourceId build() { ResourceId resourceId = autoBuild(); Preconditions.checkState( 0 <= resourceId.getEntryId() && resourceId.getEntryId() <= MAX_ENTRY_ID, - "Entry id not in [0, 0xffff]"); + "Entry id not in [0, 0xffff]: %s", + resourceId.getEntryId()); Preconditions.checkState( 0 < resourceId.getTypeId() && resourceId.getTypeId() <= MAX_TYPE_ID, - "Type id not in [1, 0xff]"); + "Type id not in [1, 0xff]: %s", + resourceId.getTypeId()); Preconditions.checkState( 0 <= resourceId.getPackageId() && resourceId.getPackageId() <= MAX_PACKAGE_ID, - "Package id not in [0, 0xff]"); + "Package id not in [0, 0xff]: %s", + resourceId.getPackageId()); return resourceId; } } @@ -103,4 +121,9 @@ public ResourceId build() { public int getFullResourceId() { return (getPackageId() << PACKAGE_SHIFT) + (getTypeId() << TYPE_SHIFT) + getEntryId(); } + + @Override + public final String toString() { + return String.format("0x%08X", getFullResourceId()); + } } diff --git a/src/main/java/com/android/tools/build/bundletool/model/ResourceInjector.java b/src/main/java/com/android/tools/build/bundletool/model/ResourceInjector.java index ddbc29c5..f64a50c3 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ResourceInjector.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ResourceInjector.java @@ -23,7 +23,7 @@ import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.Type; import com.android.aapt.Resources.TypeId; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import java.util.List; /** diff --git a/src/main/java/com/android/tools/build/bundletool/model/ResourceTableEntry.java b/src/main/java/com/android/tools/build/bundletool/model/ResourceTableEntry.java index 84c56bde..be52d2df 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ResourceTableEntry.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ResourceTableEntry.java @@ -20,9 +20,12 @@ import com.android.aapt.Resources.Type; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; +import com.google.errorprone.annotations.Immutable; /** Represents an entry in a resource table. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ResourceTableEntry { public static ResourceTableEntry create(Resources.Package pkg, Type type, Entry entry) { diff --git a/src/main/java/com/android/tools/build/bundletool/model/SigningConfiguration.java b/src/main/java/com/android/tools/build/bundletool/model/SigningConfiguration.java index 448b1bca..a35d5c9c 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/SigningConfiguration.java +++ b/src/main/java/com/android/tools/build/bundletool/model/SigningConfiguration.java @@ -15,13 +15,13 @@ */ package com.android.tools.build.bundletool.model; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.google.common.collect.ImmutableList.toImmutableList; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.utils.flags.Flag.Password; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -40,7 +40,10 @@ import javax.security.auth.DestroyFailedException; /** Information required to sign an APK. */ +@SuppressWarnings("Immutable") // PrivateKey and X509Certificate are considered immutable. +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class SigningConfiguration { SigningConfiguration() {} diff --git a/src/main/java/com/android/tools/build/bundletool/model/SizeConfiguration.java b/src/main/java/com/android/tools/build/bundletool/model/SizeConfiguration.java index 98ee6c77..9ae1971e 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/SizeConfiguration.java +++ b/src/main/java/com/android/tools/build/bundletool/model/SizeConfiguration.java @@ -17,26 +17,27 @@ package com.android.tools.build.bundletool.model; import static com.android.bundle.Targeting.ScreenDensity.DensityOneofCase.DENSITY_ALIAS; -import static com.android.tools.build.bundletool.targeting.TargetingUtils.getMaxSdk; -import static com.android.tools.build.bundletool.targeting.TargetingUtils.getMinSdk; +import static com.android.tools.build.bundletool.model.targeting.TargetingUtils.getMaxSdk; +import static com.android.tools.build.bundletool.model.targeting.TargetingUtils.getMinSdk; import com.android.bundle.Targeting.AbiTargeting; import com.android.bundle.Targeting.LanguageTargeting; import com.android.bundle.Targeting.ScreenDensity; import com.android.bundle.Targeting.ScreenDensityTargeting; import com.android.bundle.Targeting.SdkVersionTargeting; -import com.android.tools.build.bundletool.commands.GetSizeCommand; import com.google.auto.value.AutoValue; import com.google.common.collect.Iterables; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** - * Configuration representing different targetings in a readable format, to be outputted by the - * {@link GetSizeCommand}. Ex: {sdk=21-22,locale=en}. + * Configuration representing different targetings in a readable format. Ex: {sdk=21-22,locale=en}. * - *

We set only the configuration fields based on the dimensions passed to {@link GetSizeCommand}. + *

We set only the configuration fields based on provided dimensions. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class SizeConfiguration { public abstract Optional getAbi(); diff --git a/src/main/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilder.java index 5bdca7e3..4f08fdf5 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilder.java @@ -17,10 +17,10 @@ package com.android.tools.build.bundletool.model; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.google.common.collect.TreeBasedTable; /** diff --git a/src/main/java/com/android/tools/build/bundletool/model/UserCountriesCondition.java b/src/main/java/com/android/tools/build/bundletool/model/UserCountriesCondition.java new file mode 100755 index 00000000..431f5fbb --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/model/UserCountriesCondition.java @@ -0,0 +1,42 @@ +/* + * 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; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; + +/** + * A {@link BundleModule} condition describing a set of user countries. + * + *

The list of country codes can be inclusive or exclusive. The country codes must follow the + * CLDR territory two-letter string format. + */ +@Immutable +@AutoValue +@AutoValue.CopyAnnotations +public abstract class UserCountriesCondition { + + public abstract ImmutableList getCountries(); + + public abstract boolean getExclude(); + + public static UserCountriesCondition create( + ImmutableList countryCodeList, boolean exclude) { + return new AutoValue_UserCountriesCondition(countryCodeList, exclude); + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/model/VariantKey.java b/src/main/java/com/android/tools/build/bundletool/model/VariantKey.java index b2456094..663275b5 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/VariantKey.java +++ b/src/main/java/com/android/tools/build/bundletool/model/VariantKey.java @@ -20,13 +20,14 @@ import static com.android.tools.build.bundletool.model.ModuleSplit.SplitType.SPLIT; import static com.android.tools.build.bundletool.model.ModuleSplit.SplitType.STANDALONE; import static com.android.tools.build.bundletool.model.ModuleSplit.SplitType.SYSTEM; -import static com.android.tools.build.bundletool.targeting.TargetingComparators.VARIANT_TARGETING_COMPARATOR; +import static com.android.tools.build.bundletool.model.targeting.TargetingComparators.VARIANT_TARGETING_COMPARATOR; import static java.util.Comparator.comparing; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.google.auto.value.AutoValue; import com.google.common.collect.Ordering; +import com.google.errorprone.annotations.Immutable; /** * Key identifying a variant. @@ -34,7 +35,9 @@ *

A variant is a set of APKs. One device is guaranteed to receive only APKs from the same * variant. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class VariantKey implements Comparable { public static VariantKey create(ModuleSplit moduleSplit) { return new AutoValue_VariantKey(moduleSplit.getSplitType(), moduleSplit.getVariantTargeting()); diff --git a/src/main/java/com/android/tools/build/bundletool/model/WearApkLocator.java b/src/main/java/com/android/tools/build/bundletool/model/WearApkLocator.java index a1aa4239..962b3816 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/WearApkLocator.java +++ b/src/main/java/com/android/tools/build/bundletool/model/WearApkLocator.java @@ -21,13 +21,14 @@ import com.android.aapt.Resources.Entry; import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.ResourcesUtils; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +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.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.google.common.collect.Iterables; import com.google.protobuf.InvalidProtocolBufferException; import java.io.IOException; +import java.io.InputStream; import java.io.UncheckedIOException; import java.util.Optional; @@ -127,8 +128,8 @@ private static ModuleEntry findXmlDescriptionZipEntry(ModuleSplit split, String */ private static Optional extractWearApkName(ModuleEntry wearApkDescriptionXmlEntry) { XmlProtoNode root; - try { - root = new XmlProtoNode(XmlNode.parseFrom(wearApkDescriptionXmlEntry.getContent())); + try (InputStream content = wearApkDescriptionXmlEntry.getContent()) { + root = new XmlProtoNode(XmlNode.parseFrom(content)); } catch (InvalidProtocolBufferException e) { throw ValidationException.builder() .withCause(e) diff --git a/src/main/java/com/android/tools/build/bundletool/model/ZipPath.java b/src/main/java/com/android/tools/build/bundletool/model/ZipPath.java index 9c19eba5..4d11389c 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ZipPath.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ZipPath.java @@ -19,12 +19,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import com.google.auto.value.AutoValue; import com.google.common.base.Joiner; import com.google.common.base.Splitter; -import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Comparators; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ObjectArrays; -import com.google.common.collect.Ordering; +import com.google.errorprone.annotations.Immutable; import java.io.File; import java.io.IOException; import java.net.URI; @@ -34,9 +35,8 @@ import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; -import java.util.Arrays; +import java.util.Comparator; import java.util.Iterator; -import java.util.List; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; @@ -45,7 +45,10 @@ * *

The separator will always be a forward slash ("/") regardless of the platform being used. */ -public final class ZipPath implements Path { +@Immutable +@AutoValue +@AutoValue.CopyAnnotations +public abstract class ZipPath implements Path { private static final String SEPARATOR = "/"; private static final Splitter SPLITTER = Splitter.on(SEPARATOR).omitEmptyStrings(); @@ -54,34 +57,27 @@ public final class ZipPath implements Path { public static final ZipPath ROOT = ZipPath.create(""); + // Constructor with restricted visibility to avoid subclassing and ensure immutability. + ZipPath() {} + /** * List of parts of the path separated by the separator. * *

Note that this list can be empty when denoting the root of the zip. */ - protected final String[] names; - - // Cached hash code. - private transient int hashCode; - - private ZipPath(List names) { - this(names.toArray(new String[0])); - } - - private ZipPath(String[] names) { - Arrays.stream(names) - .forEach( - name -> - checkArgument( - !FORBIDDEN_NAMES.contains(name), - "Name '%s' is not supported inside path.", - name)); - this.names = names; - } + abstract ImmutableList getNames(); public static ZipPath create(String path) { checkNotNull(path, "Path cannot be null."); - return new ZipPath(SPLITTER.splitToList(path)); + return create(ImmutableList.copyOf(SPLITTER.splitToList(path))); + } + + private static ZipPath create(ImmutableList names) { + names.forEach( + name -> + checkArgument( + !FORBIDDEN_NAMES.contains(name), "Name '%s' is not supported inside path.", name)); + return new AutoValue_ZipPath(names); } @Override @@ -89,7 +85,8 @@ public static ZipPath create(String path) { public ZipPath resolve(Path p) { checkNotNull(p, "Path cannot be null."); ZipPath path = (ZipPath) p; - return new ZipPath(ObjectArrays.concat(names, path.names, String.class)); + return create( + ImmutableList.builder().addAll(getNames()).addAll(path.getNames()).build()); } @Override @@ -102,7 +99,7 @@ public ZipPath resolve(String path) { @CheckReturnValue public ZipPath resolveSibling(Path path) { checkNotNull(path, "Path cannot be null."); - checkState(names.length > 0, "Root has not sibling."); + checkState(!getNames().isEmpty(), "Root has not sibling."); return getParent().resolve(path); } @@ -115,25 +112,25 @@ public ZipPath resolveSibling(String path) { @Override @CheckReturnValue public ZipPath subpath(int from, int to) { - checkArgument(from >= 0 && from < names.length); - checkArgument(to >= 0 && to <= names.length); + checkArgument(from >= 0 && from < getNames().size()); + checkArgument(to >= 0 && to <= getNames().size()); checkArgument(from < to); - return new ZipPath(Arrays.copyOfRange(names, from, to)); + return create(getNames().subList(from, to)); } @Override @Nullable @CheckReturnValue public ZipPath getParent() { - if (names.length == 0) { + if (getNames().isEmpty()) { return null; } - return new ZipPath(Arrays.copyOf(names, names.length - 1)); + return create(getNames().subList(0, getNames().size() - 1)); } @Override public int getNameCount() { - return names.length; + return getNames().size(); } @Override @@ -143,8 +140,8 @@ public ZipPath getRoot() { @Override public ZipPath getName(int index) { - checkArgument(index >= 0 && index < names.length); - return ZipPath.create(names[index]); + checkArgument(index >= 0 && index < getNames().size()); + return ZipPath.create(getNames().get(index)); } @Override @@ -154,8 +151,10 @@ public boolean startsWith(Path p) { return false; } + ImmutableList names = getNames(); + ImmutableList otherNames = path.getNames(); for (int i = 0; i < path.getNameCount(); i++) { - if (!path.names[i].equals(names[i])) { + if (!otherNames.get(i).equals(names.get(i))) { return false; } } @@ -175,8 +174,10 @@ public boolean endsWith(Path p) { return false; } + ImmutableList names = getNames(); + ImmutableList otherNames = path.getNames(); for (int i = 0; i < path.getNameCount(); i++) { - if (!path.names[path.names.length - i - 1].equals(names[names.length - i - 1])) { + if (!otherNames.get(otherNames.size() - i - 1).equals(names.get(names.size() - i - 1))) { return false; } } @@ -190,38 +191,15 @@ public boolean endsWith(String p) { } @Override - public int hashCode() { - if (hashCode == 0) { - hashCode = Arrays.hashCode(names); - } - return hashCode; - } - - @Override - public boolean equals(Object path) { - if (!(path instanceof ZipPath)) { - return false; - } - return Arrays.equals(names, ((ZipPath) path).names); - } - - @Override - public int compareTo(Path other) { - ZipPath path = (ZipPath) other; - ComparisonChain chain = ComparisonChain.start(); - for (int i = 0; i < Math.max(getNameCount(), path.getNameCount()); i++) { - chain = - chain.compare( - i < names.length ? names[i] : null, - i < path.names.length ? path.names[i] : null, - Ordering.natural().nullsFirst()); - } - return chain.result(); + public final int compareTo(Path other) { + return Comparators.lexicographical(Comparator.naturalOrder()) + .compare(getNames(), ((ZipPath) other).getNames()); } + /** Returns the path as used in the zip file. */ @Override - public String toString() { - return JOINER.join(names); + public final String toString() { + return JOINER.join(getNames()); } @Override @@ -232,7 +210,7 @@ public ZipPath getFileName() { @Override public Iterator iterator() { - return Arrays.stream(names).map(name -> (Path) ZipPath.create(name)).iterator(); + return getNames().stream().map(name -> (Path) ZipPath.create(name)).iterator(); } @Override diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/BundleFileTypesException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleFileTypesException.java similarity index 99% rename from src/main/java/com/android/tools/build/bundletool/exceptions/BundleFileTypesException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleFileTypesException.java index 7f3eb8e4..95d5cca2 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/BundleFileTypesException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleFileTypesException.java @@ -12,7 +12,7 @@ * the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/CommandExecutionException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/CommandExecutionException.java similarity index 97% rename from src/main/java/com/android/tools/build/bundletool/exceptions/CommandExecutionException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/CommandExecutionException.java index 01341146..972cc97d 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/CommandExecutionException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/CommandExecutionException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/DeviceNotFoundException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/DeviceNotFoundException.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/exceptions/DeviceNotFoundException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/DeviceNotFoundException.java index 4777c2c8..c7780f08 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/DeviceNotFoundException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/DeviceNotFoundException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/InstallationException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/InstallationException.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/exceptions/InstallationException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/InstallationException.java index e7bec453..9b856139 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/InstallationException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/InstallationException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import javax.annotation.CheckReturnValue; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/ParseException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/ParseException.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/exceptions/ParseException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/ParseException.java index 16df154f..5a9391d1 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/ParseException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/ParseException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import javax.annotation.CheckReturnValue; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/ResouceTableException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/ResouceTableException.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/exceptions/ResouceTableException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/ResouceTableException.java index 1b97a37a..372bc0c2 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/ResouceTableException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/ResouceTableException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.stream.Collectors.toList; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/ValidationException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/ValidationException.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/exceptions/ValidationException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/ValidationException.java index b7d7045e..6ee84a06 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/ValidationException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/ValidationException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; import javax.annotation.CheckReturnValue; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/XmlParsingException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/XmlParsingException.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/exceptions/XmlParsingException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/XmlParsingException.java index 4b01c0c1..10590900 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/XmlParsingException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/XmlParsingException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.exceptions; +package com.android.tools.build.bundletool.model.exceptions; /** Exception thrown when parsing XML goes wrong. */ public class XmlParsingException extends ValidationException { diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestDuplicateAttributeException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestDuplicateAttributeException.java similarity index 92% rename from src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestDuplicateAttributeException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestDuplicateAttributeException.java index 104438b4..d6cd60bf 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestDuplicateAttributeException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestDuplicateAttributeException.java @@ -14,13 +14,13 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions.manifest; +package com.android.tools.build.bundletool.model.exceptions.manifest; import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Errors.BundleToolError; import com.android.bundle.Errors.ManifestDuplicateAttributeError; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; import com.google.common.collect.ImmutableSet; /** diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestFusingException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestFusingException.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestFusingException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestFusingException.java index b809ae6e..279ec39e 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestFusingException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestFusingException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions.manifest; +package com.android.tools.build.bundletool.model.exceptions.manifest; import com.android.bundle.Errors.BundleToolError; import com.android.bundle.Errors.ManifestBaseModuleExcludedFromFusingError; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestSdkTargetingException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestSdkTargetingException.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestSdkTargetingException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestSdkTargetingException.java index af23e01a..00e9ed18 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestSdkTargetingException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestSdkTargetingException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions.manifest; +package com.android.tools.build.bundletool.model.exceptions.manifest; import com.android.aapt.Resources.XmlAttribute; import com.android.bundle.Errors.BundleToolError; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestValidationException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestValidationException.java similarity index 87% rename from src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestValidationException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestValidationException.java index def043f6..30570f26 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestValidationException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestValidationException.java @@ -12,11 +12,11 @@ * the License */ -package com.android.tools.build.bundletool.exceptions.manifest; +package com.android.tools.build.bundletool.model.exceptions.manifest; import static com.google.common.base.Preconditions.checkNotNull; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.errorprone.annotations.FormatMethod; import com.google.errorprone.annotations.FormatString; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestVersionCodeConflictException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestVersionCodeConflictException.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestVersionCodeConflictException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestVersionCodeConflictException.java index a6c57af9..d152c966 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestVersionCodeConflictException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestVersionCodeConflictException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions.manifest; +package com.android.tools.build.bundletool.model.exceptions.manifest; import com.android.bundle.Errors.BundleToolError; import com.android.bundle.Errors.ManifestModulesDifferentVersionCodes; diff --git a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestVersionException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestVersionException.java similarity index 97% rename from src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestVersionException.java rename to src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestVersionException.java index 2bff18b2..52c2f12a 100755 --- a/src/main/java/com/android/tools/build/bundletool/exceptions/manifest/ManifestVersionException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/manifest/ManifestVersionException.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.exceptions.manifest; +package com.android.tools.build.bundletool.model.exceptions.manifest; import com.android.aapt.Resources.XmlAttribute; import com.android.bundle.Errors.BundleToolError; diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/AlternativeVariantTargetingPopulator.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/AlternativeVariantTargetingPopulator.java similarity index 99% rename from src/main/java/com/android/tools/build/bundletool/targeting/AlternativeVariantTargetingPopulator.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/AlternativeVariantTargetingPopulator.java index 616538e7..c6351e7a 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/AlternativeVariantTargetingPopulator.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/AlternativeVariantTargetingPopulator.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/ScreenDensitySelector.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/ScreenDensitySelector.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/targeting/ScreenDensitySelector.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/ScreenDensitySelector.java index 4022cbd3..bf94eef0 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/ScreenDensitySelector.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/ScreenDensitySelector.java @@ -14,12 +14,12 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.ANY_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.MDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.ANY_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MDPI_VALUE; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -27,7 +27,7 @@ import com.android.aapt.Resources.ConfigValue; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; -import com.android.tools.build.bundletool.utils.ResourcesUtils; +import com.android.tools.build.bundletool.model.utils.ResourcesUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/TargetedDirectory.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectory.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/targeting/TargetedDirectory.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectory.java index 1b177a96..1202aa1e 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/TargetedDirectory.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectory.java @@ -14,17 +14,18 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Streams; +import com.google.errorprone.annotations.Immutable; import java.util.HashSet; import java.util.Set; @@ -34,7 +35,9 @@ *

Each directory within assets can have the "name" or "name#key_value" form. The latter form * allows to associate the targeting to the directory. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class TargetedDirectory { public abstract ImmutableList getPathSegments(); diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/TargetedDirectorySegment.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectorySegment.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/targeting/TargetedDirectorySegment.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectorySegment.java index 0abd88c5..9680af63 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/TargetedDirectorySegment.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectorySegment.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -22,19 +22,22 @@ import com.android.bundle.Targeting.AssetsDirectoryTargeting; import com.android.bundle.Targeting.GraphicsApiTargeting; import com.android.bundle.Targeting.LanguageTargeting; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.GraphicsApiUtils; -import com.android.tools.build.bundletool.utils.TextureCompressionUtils; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.GraphicsApiUtils; +import com.android.tools.build.bundletool.model.utils.TextureCompressionUtils; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; /** A single parsed name of the assets directory path. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class TargetedDirectorySegment { private static final Pattern DIRECTORY_SEGMENT_PATTERN = diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingComparators.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingComparators.java similarity index 97% rename from src/main/java/com/android/tools/build/bundletool/targeting/TargetingComparators.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingComparators.java index a5f3d30a..47c8ba6d 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingComparators.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingComparators.java @@ -14,9 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.getScreenDensityDpi; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.getScreenDensityDpi; import static com.google.common.collect.Comparators.emptiesFirst; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.util.Comparator.comparing; diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingDimension.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingDimension.java similarity index 92% rename from src/main/java/com/android/tools/build/bundletool/targeting/TargetingDimension.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingDimension.java index cde602f8..892151fa 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingDimension.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingDimension.java @@ -14,8 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; - +package com.android.tools.build.bundletool.model.targeting; /** * A dimension recognized by the bundletool for targeting. diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingGenerator.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingGenerator.java similarity index 97% rename from src/main/java/com/android/tools/build/bundletool/targeting/TargetingGenerator.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingGenerator.java index c135089a..ebbe3cd5 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingGenerator.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingGenerator.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.android.tools.build.bundletool.model.BundleModule.ABI_SPLITTER; import static com.google.common.base.Preconditions.checkArgument; @@ -33,11 +33,11 @@ import com.android.bundle.Targeting.MultiAbi; import com.android.bundle.Targeting.MultiAbiTargeting; import com.android.bundle.Targeting.NativeDirectoryTargeting; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.TargetingProtoUtils; -import com.android.tools.build.bundletool.utils.files.FileUtils; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.TargetingProtoUtils; +import com.android.tools.build.bundletool.model.utils.files.FileUtils; import com.google.common.base.Ascii; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; diff --git a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingUtils.java b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingUtils.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/targeting/TargetingUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingUtils.java index c165ce6d..dec24edb 100755 --- a/src/main/java/com/android/tools/build/bundletool/targeting/TargetingUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/targeting/TargetingUtils.java @@ -14,9 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionTargeting; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.sdkVersionTargeting; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -25,7 +25,7 @@ import com.android.bundle.Targeting.SdkVersion; import com.android.bundle.Targeting.SdkVersionTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.utils.TargetingProtoUtils; +import com.android.tools.build.bundletool.model.utils.TargetingProtoUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ApkSizeUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ApkSizeUtils.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/ApkSizeUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ApkSizeUtils.java index 61c0485e..459258b0 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ApkSizeUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ApkSizeUtils.java @@ -14,9 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.utils.ZipUtils.calculateGzipCompressedSize; +import static com.android.tools.build.bundletool.model.utils.ZipUtils.calculateGzipCompressedSize; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/CollectorUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/CollectorUtils.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/utils/CollectorUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/CollectorUtils.java index c919c879..71e54ae5 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/CollectorUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/CollectorUtils.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.utils.ConcurrencyUtils.waitFor; +import static com.android.tools.build.bundletool.model.utils.ConcurrencyUtils.waitFor; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.function.Function.identity; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ConcurrencyUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ConcurrencyUtils.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/ConcurrencyUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ConcurrencyUtils.java index d87f59fd..30868a9c 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ConcurrencyUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ConcurrencyUtils.java @@ -14,9 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/CsvFormatter.java b/src/main/java/com/android/tools/build/bundletool/model/utils/CsvFormatter.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/CsvFormatter.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/CsvFormatter.java index 843d6704..8ba4c954 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/CsvFormatter.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/CsvFormatter.java @@ -14,10 +14,10 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.utils.ImmutableCollectors.toImmutableList; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.auto.value.AutoValue; import com.google.common.base.CharMatcher; @@ -25,11 +25,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.escape.CharEscaperBuilder; import com.google.common.escape.Escaper; +import com.google.errorprone.annotations.Immutable; import java.util.Collection; import java.util.Optional; /** Class to format the output to CSV */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class CsvFormatter { private static final Joiner COMMA_JOINER = Joiner.on(","); diff --git a/src/main/java/com/android/tools/build/bundletool/utils/SystemEnvironmentVariableProvider.java b/src/main/java/com/android/tools/build/bundletool/model/utils/DefaultSystemEnvironmentProvider.java similarity index 67% rename from src/main/java/com/android/tools/build/bundletool/utils/SystemEnvironmentVariableProvider.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/DefaultSystemEnvironmentProvider.java index 9a947a78..6b8fbfd8 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/SystemEnvironmentVariableProvider.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/DefaultSystemEnvironmentProvider.java @@ -14,15 +14,20 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import java.util.Optional; -/** Default implementation of {@link EnvironmentVariableProvider] using the {@link System} class. */ -public class SystemEnvironmentVariableProvider implements EnvironmentVariableProvider { +/** Default implementation of {@link SystemEnvironmentProvider] using the {@link System} class. */ +public class DefaultSystemEnvironmentProvider implements SystemEnvironmentProvider { @Override public Optional getVariable(String name) { return Optional.ofNullable(System.getenv(name)); } + + @Override + public Optional getProperty(String name) { + return Optional.ofNullable(System.getProperty(name)); + } } diff --git a/src/main/java/com/android/tools/build/bundletool/utils/EnumMapper.java b/src/main/java/com/android/tools/build/bundletool/model/utils/EnumMapper.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/utils/EnumMapper.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/EnumMapper.java index 5d63f8d6..dda62ab0 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/EnumMapper.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/EnumMapper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.base.Predicates.not; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/FileNames.java b/src/main/java/com/android/tools/build/bundletool/model/utils/FileNames.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/FileNames.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/FileNames.java index 9702900c..2016b3eb 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/FileNames.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/FileNames.java @@ -14,8 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; - +package com.android.tools.build.bundletool.model.utils; /** Common constants used for file names by the bundletool. */ public final class FileNames { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/GetSizeCsvUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/GetSizeCsvUtils.java similarity index 91% rename from src/main/java/com/android/tools/build/bundletool/utils/GetSizeCsvUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/GetSizeCsvUtils.java index 27a76eec..4bd61bb3 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/GetSizeCsvUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/GetSizeCsvUtils.java @@ -14,14 +14,13 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.utils.ImmutableCollectors.toImmutableList; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; -import com.android.tools.build.bundletool.commands.GetSizeCommand; -import com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension; import com.android.tools.build.bundletool.model.ConfigurationSizes; +import com.android.tools.build.bundletool.model.GetSizeRequest.Dimension; import com.android.tools.build.bundletool.model.SizeConfiguration; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -31,7 +30,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; -/** Utils to format output of {@link GetSizeCommand} into CSV format. */ +/** Utils to format output of size information into CSV format. */ public final class GetSizeCsvUtils { private static final Ordering DIMENSIONS_COMPARATOR = diff --git a/src/main/java/com/android/tools/build/bundletool/utils/GraphicsApiUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/GraphicsApiUtils.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/utils/GraphicsApiUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/GraphicsApiUtils.java index 67f6ae7a..85031f2d 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/GraphicsApiUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/GraphicsApiUtils.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import com.android.bundle.Targeting.GraphicsApi; import com.android.bundle.Targeting.GraphicsApiTargeting; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/OsPlatform.java b/src/main/java/com/android/tools/build/bundletool/model/utils/OsPlatform.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/OsPlatform.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/OsPlatform.java index 506c71fe..471c7b3e 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/OsPlatform.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/OsPlatform.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; /** OS platforms. */ public enum OsPlatform { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ProtoUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ProtoUtils.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/ProtoUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ProtoUtils.java index e605f2f0..a5f71626 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ProtoUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ProtoUtils.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import com.google.protobuf.Message; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ResourcesUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ResourcesUtils.java similarity index 92% rename from src/main/java/com/android/tools/build/bundletool/utils/ResourcesUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ResourcesUtils.java index 5a32a2fe..31a8c013 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ResourcesUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ResourcesUtils.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.MoreCollectors.toOptional; @@ -89,16 +89,16 @@ public final class ResourcesUtils { * gets removed from the table altogether. * * @param originalTable the original resource table - * @param removeTypePredicate determines whether a type should be completely removed, regardless - * of its contents + * @param removeEntryPredicate determines whether an entry should be completely removed with all + * their configurations * @param configValuesFilterFn computes a new {@link Entry} with filtered {@link ConfigValue} list * (possibly empty) * @return filtered resource table */ public static ResourceTable filterResourceTable( ResourceTable originalTable, - Predicate removeTypePredicate, - Function configValuesFilterFn) { + Predicate removeEntryPredicate, + Function configValuesFilterFn) { ResourceTable.Builder filteredTable = originalTable.toBuilder(); @@ -106,14 +106,16 @@ public static ResourceTable filterResourceTable( Package.Builder pkg = filteredTable.getPackageBuilder(pkgIdx); for (int typeIdx = pkg.getTypeCount() - 1; typeIdx >= 0; typeIdx--) { - if (removeTypePredicate.test(pkg.getType(typeIdx))) { - pkg.removeType(typeIdx); - continue; - } Type.Builder type = pkg.getTypeBuilder(typeIdx); for (int entryIdx = type.getEntryCount() - 1; entryIdx >= 0; entryIdx--) { - Entry entry = type.getEntry(entryIdx); + ResourceTableEntry entry = + ResourceTableEntry.create( + filteredTable.getPackage(pkgIdx), pkg.getType(typeIdx), type.getEntry(entryIdx)); + if (removeEntryPredicate.test(entry)) { + type.removeEntry(entryIdx); + continue; + } Entry filteredEntry = configValuesFilterFn.apply(entry); if (filteredEntry.getConfigValueCount() > 0) { type.setEntry(entryIdx, filteredEntry); diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ResultUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ResultUtils.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/ResultUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ResultUtils.java index 4702f4ce..cf646698 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ResultUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ResultUtils.java @@ -14,15 +14,15 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.utils.FileNames.TABLE_OF_CONTENTS_FILE; +import static com.android.tools.build.bundletool.model.utils.FileNames.TABLE_OF_CONTENTS_FILE; import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.bundle.Commands.ApkDescription; import com.android.bundle.Commands.BuildApksResult; import com.android.bundle.Commands.Variant; -import com.android.tools.build.bundletool.utils.files.BufferedIo; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.common.collect.ImmutableList; import java.io.FileInputStream; import java.io.IOException; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/SdkToolsLocator.java b/src/main/java/com/android/tools/build/bundletool/model/utils/SdkToolsLocator.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/utils/SdkToolsLocator.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/SdkToolsLocator.java index 62d707c5..a1161bb7 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/SdkToolsLocator.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/SdkToolsLocator.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.MoreCollectors.onlyElement; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableMap; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/SplitsXmlInjector.java b/src/main/java/com/android/tools/build/bundletool/model/utils/SplitsXmlInjector.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/utils/SplitsXmlInjector.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/SplitsXmlInjector.java index 77a66d69..6b12d844 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/SplitsXmlInjector.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/SplitsXmlInjector.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -39,7 +39,7 @@ /** * Injects a splits{N}.xml resource in master base module with language -> split ID mapping. * - *

It is done only for split APKs, not instant APKs nor standalone APKs. + *

It is done only for split APK and standalone APKs, not instant APKs. */ public class SplitsXmlInjector { @@ -64,6 +64,8 @@ public GeneratedApks process(GeneratedApks generatedApks) { .collect(toImmutableList()); case INSTANT: return keySplit.getValue(); + case ASSET_SLICE: + throw new IllegalStateException("Unexpected Asset Slice inside variant."); } throw new IllegalStateException( String.format("Unknown split type %s", keySplit.getKey().getSplitType())); diff --git a/src/main/java/com/android/tools/build/bundletool/utils/EnvironmentVariableProvider.java b/src/main/java/com/android/tools/build/bundletool/model/utils/SystemEnvironmentProvider.java similarity index 70% rename from src/main/java/com/android/tools/build/bundletool/utils/EnvironmentVariableProvider.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/SystemEnvironmentProvider.java index 4134e3f2..b532925b 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/EnvironmentVariableProvider.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/SystemEnvironmentProvider.java @@ -14,20 +14,23 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import java.util.Optional; -/** Interface for fetching the environment variables. */ -public interface EnvironmentVariableProvider { +/** Interface for fetching the environment variables and system properties. */ +public interface SystemEnvironmentProvider { - EnvironmentVariableProvider DEFAULT_PROVIDER = new SystemEnvironmentVariableProvider(); + SystemEnvironmentProvider DEFAULT_PROVIDER = new DefaultSystemEnvironmentProvider(); String ANDROID_HOME = "ANDROID_HOME"; /** Returns the value of the environment variable of a given name if it exists. */ Optional getVariable(String name); + /** Returns the value of the system property if it exists. */ + Optional getProperty(String name); + default Optional getAndroidHomePath() { return getVariable(ANDROID_HOME); } diff --git a/src/main/java/com/android/tools/build/bundletool/utils/TargetingProtoUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/TargetingProtoUtils.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/TargetingProtoUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/TargetingProtoUtils.java index bdd2fa58..51fa7311 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/TargetingProtoUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/TargetingProtoUtils.java @@ -14,11 +14,11 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.android.bundle.Targeting.ScreenDensity.DensityOneofCase.DENSITY_ALIAS; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_L_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_L_API_VERSION; import com.android.bundle.Targeting.Abi; import com.android.bundle.Targeting.ApkTargeting; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/TextureCompressionUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/TextureCompressionUtils.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/utils/TextureCompressionUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/TextureCompressionUtils.java index 98a99c20..2bccbbbe 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/TextureCompressionUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/TextureCompressionUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import com.android.bundle.Targeting.TextureCompressionFormat; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ThrowableUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ThrowableUtils.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/utils/ThrowableUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ThrowableUtils.java index c568dda9..7ffcbae9 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ThrowableUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ThrowableUtils.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import com.google.common.base.Throwables; import java.util.Arrays; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/Versions.java b/src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/Versions.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java index 8ef70b77..542ff30b 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/Versions.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; /** Various version constants and versions-related code used by the bundletool. */ public final class Versions { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/ZipUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ZipUtils.java similarity index 93% rename from src/main/java/com/android/tools/build/bundletool/utils/ZipUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/ZipUtils.java index ce457fdd..8d54a4e9 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/ZipUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ZipUtils.java @@ -14,9 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.google.common.base.Predicates.not; import com.android.tools.build.bundletool.model.ZipPath; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/files/BufferedIo.java b/src/main/java/com/android/tools/build/bundletool/model/utils/files/BufferedIo.java similarity index 62% rename from src/main/java/com/android/tools/build/bundletool/utils/files/BufferedIo.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/files/BufferedIo.java index ef4f7fa5..9ddae667 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/files/BufferedIo.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/files/BufferedIo.java @@ -14,10 +14,12 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.files; +package com.android.tools.build.bundletool.model.utils.files; import static java.nio.charset.StandardCharsets.UTF_8; +import com.android.tools.build.bundletool.model.InputStreamSupplier; +import com.google.errorprone.annotations.MustBeClosed; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -31,37 +33,58 @@ import java.nio.file.Path; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import javax.annotation.WillCloseWhenClosed; +import javax.annotation.WillNotClose; /** Utilities for efficient I/O. */ public final class BufferedIo { - public static BufferedReader reader(InputStream is) { + @MustBeClosed + public static BufferedReader reader(@WillCloseWhenClosed InputStream is) { return new BufferedReader(new InputStreamReader(is, UTF_8)); } + @MustBeClosed public static BufferedReader reader(Path file) throws IOException { return Files.newBufferedReader(file, UTF_8); } + @MustBeClosed public static InputStream inputStream(Path file) throws IOException { return makeBuffered(Files.newInputStream(file)); } - public static InputStream inputStream(ZipFile zipFile, ZipEntry zipEntry) throws IOException { + @MustBeClosed + public static InputStream inputStream(@WillNotClose ZipFile zipFile, ZipEntry zipEntry) + throws IOException { return makeBuffered(zipFile.getInputStream(zipEntry)); } + @SuppressWarnings("MustBeClosedChecker") // InputStreamSupplier is annotated with @MustBeClosed + public static InputStreamSupplier inputStreamSupplier(Path file) { + return () -> inputStream(file); + } + + @SuppressWarnings("MustBeClosedChecker") // InputStreamSupplier is annotated with @MustBeClosed + public static InputStreamSupplier inputStreamSupplier( + @WillNotClose ZipFile zipFile, ZipEntry zipEntry) { + return () -> inputStream(zipFile, zipEntry); + } + + @MustBeClosed public static OutputStream outputStream(Path file) throws IOException { return makeBuffered(Files.newOutputStream(file)); } - static InputStream makeBuffered(InputStream is) { + @MustBeClosed + static InputStream makeBuffered(@WillCloseWhenClosed InputStream is) { return (is instanceof BufferedInputStream || is instanceof ByteArrayInputStream) ? is : new BufferedInputStream(is); } - static OutputStream makeBuffered(OutputStream os) { + @MustBeClosed + static OutputStream makeBuffered(@WillCloseWhenClosed OutputStream os) { return (os instanceof BufferedOutputStream || os instanceof ByteArrayOutputStream) ? os : new BufferedOutputStream(os); diff --git a/src/main/java/com/android/tools/build/bundletool/utils/files/FilePreconditions.java b/src/main/java/com/android/tools/build/bundletool/model/utils/files/FilePreconditions.java similarity index 76% rename from src/main/java/com/android/tools/build/bundletool/utils/files/FilePreconditions.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/files/FilePreconditions.java index bfe8ee26..d3a7bbc0 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/files/FilePreconditions.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/files/FilePreconditions.java @@ -14,15 +14,12 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.files; +package com.android.tools.build.bundletool.model.utils.files; import static com.google.common.base.Preconditions.checkArgument; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; /** Common assertions about files/directories with standardized error messages. */ public final class FilePreconditions { @@ -61,26 +58,6 @@ public static void checkFileHasExtension(String fileDescription, Path path, Stri extension); } - /** - * Checks whether names of the given files are mutually unique. - * - * @param fileDescription description of the file group to be used in an error message (eg. - * "Modules", "Entries") - */ - public static void checkFileNamesAreUnique(String fileDescription, Collection paths) { - Map usedFileNames = new HashMap<>(); - paths.forEach( - path -> { - String filename = path.getFileName().toString(); - checkArgument( - usedFileNames.putIfAbsent(filename, path) == null, - "%s must have unique filenames, found duplicates: '%s' and '%s'", - fileDescription, - path, - usedFileNames.get(filename)); - }); - } - /** Checks whether the given path denotes an existing directory. */ public static void checkDirectoryExists(Path path) { checkArgument(Files.exists(path), "Directory '%s' was not found.", path); diff --git a/src/main/java/com/android/tools/build/bundletool/utils/files/FileUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/files/FileUtils.java similarity index 88% rename from src/main/java/com/android/tools/build/bundletool/utils/files/FileUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/files/FileUtils.java index 4f552519..9ba3df7f 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/files/FileUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/files/FileUtils.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.files; +package com.android.tools.build.bundletool.model.utils.files; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -82,16 +82,17 @@ public static List toPathWalkingOrder(Collection paths) { * as a difference is found. */ public static boolean equalContent(InputStream is1, InputStream is2) throws IOException { - is1 = BufferedIo.makeBuffered(is1); - is2 = BufferedIo.makeBuffered(is2); + try (InputStream bufferedInputStream1 = BufferedIo.makeBuffered(is1); + InputStream bufferedInputStream2 = BufferedIo.makeBuffered(is2)) { - int c1 = 0; - int c2 = 0; - while (c1 != -1 && c2 != -1 && c1 == c2) { - c1 = is1.read(); - c2 = is2.read(); + int c1 = 0; + int c2 = 0; + while (c1 != -1 && c2 != -1 && c1 == c2) { + c1 = bufferedInputStream1.read(); + c2 = bufferedInputStream2.read(); + } + return c1 == c2; } - return c1 == c2; } // Not meant to be instantiated. diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/UnexpectedAttributeTypeException.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/UnexpectedAttributeTypeException.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/UnexpectedAttributeTypeException.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/UnexpectedAttributeTypeException.java index 6406f6d7..50f9fffc 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/UnexpectedAttributeTypeException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/UnexpectedAttributeTypeException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import com.android.aapt.Resources.XmlAttributeOrBuilder; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttribute.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttribute.java similarity index 94% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttribute.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttribute.java index e4b3293b..7f4f5bb6 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttribute.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttribute.java @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.android.tools.build.bundletool.model.AndroidManifest.NO_NAMESPACE_URI; import static com.google.common.base.Preconditions.checkNotNull; import com.android.aapt.Resources.XmlAttribute; +import com.google.errorprone.annotations.Immutable; /** Wrapper around the {@link XmlAttribute} proto, providing a fluent API. */ +@Immutable public final class XmlProtoAttribute extends XmlProtoAttributeOrBuilder { private final XmlAttribute attribute; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeBuilder.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeBuilder.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeBuilder.java index 151ae6ab..6956fb19 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.android.tools.build.bundletool.model.AndroidManifest.NO_NAMESPACE_URI; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeOrBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeOrBuilder.java similarity index 98% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeOrBuilder.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeOrBuilder.java index 77ce537b..b19ec436 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeOrBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeOrBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import com.android.aapt.Resources.Item.ValueCase; import com.android.aapt.Resources.Primitive; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElement.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElement.java similarity index 95% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElement.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElement.java index 5ba7f7a9..1bcf41ac 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElement.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElement.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.NO_NAMESPACE_URI; import static com.google.common.base.Preconditions.checkNotNull; @@ -21,9 +21,11 @@ import com.android.aapt.Resources.XmlAttribute; import com.android.aapt.Resources.XmlElement; import com.android.aapt.Resources.XmlNode; +import com.google.errorprone.annotations.Immutable; import java.util.List; /** Wrapper around the {@link XmlElement} proto, providing a fluent API. */ +@Immutable public final class XmlProtoElement extends XmlProtoElementOrBuilder< XmlNode, XmlProtoNode, XmlElement, XmlProtoElement, XmlAttribute, XmlProtoAttribute> { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementBuilder.java similarity index 90% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementBuilder.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementBuilder.java index f0c76721..b4c8a29b 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementBuilder.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.android.tools.build.bundletool.model.AndroidManifest.NO_NAMESPACE_URI; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.aapt.Resources.XmlAttribute; import com.android.aapt.Resources.XmlElement; import com.android.aapt.Resources.XmlNamespace; import com.android.aapt.Resources.XmlNode; +import com.google.common.collect.ImmutableList; import java.util.List; import java.util.function.Predicate; import java.util.function.Supplier; @@ -203,4 +205,17 @@ public XmlProtoElementBuilder addNamespaceDeclaration(String prefix, String uri) element.addNamespaceDeclaration(XmlNamespace.newBuilder().setPrefix(prefix).setUri(uri)); return this; } + + /** Removes XML elements among the direct children that satisfies the given predicate. */ + public XmlProtoElementBuilder removeChildrenElementsIf(Predicate filter) { + ImmutableList keptChildren = + getChildren() + .filter(filter.negate()) + .map(builder -> builder.build().getProto()) + .collect(toImmutableList()); + if (getProtoChildrenList().size() != keptChildren.size()) { + element.clearChild().addAllChild(keptChildren); + } + return this; + } } diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementOrBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementOrBuilder.java similarity index 99% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementOrBuilder.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementOrBuilder.java index 2ec8728a..a9773fa3 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementOrBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementOrBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.NO_NAMESPACE_URI; import static com.google.common.collect.MoreCollectors.toOptional; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoException.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoException.java similarity index 85% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoException.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoException.java index c1d1db08..ec7d3cfd 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoException.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoException.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; /** Exception thrown when an error occurs on reading or writing the XML Proto format. */ public class XmlProtoException extends ValidationException { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNamespace.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNamespace.java similarity index 92% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNamespace.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNamespace.java index e941aca8..e066e816 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNamespace.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNamespace.java @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import com.android.aapt.Resources.XmlNamespace; +import com.google.errorprone.annotations.Immutable; /** Wrapper around the {@link XmlNamespace} proto, providing a fluent API. */ +@Immutable public final class XmlProtoNamespace { private final XmlNamespace namespace; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNode.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNode.java similarity index 93% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNode.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNode.java index b98b987b..9ec16119 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNode.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNode.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.base.Preconditions.checkNotNull; import com.android.aapt.Resources.XmlElement; import com.android.aapt.Resources.XmlNode; +import com.google.errorprone.annotations.Immutable; /** Wrapper around the {@link XmlNode} proto, providing a fluent API. */ +@Immutable public final class XmlProtoNode extends XmlProtoNodeOrBuilder { diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeBuilder.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeBuilder.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeBuilder.java index 278847aa..312fd06b 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeOrBuilder.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeOrBuilder.java similarity index 96% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeOrBuilder.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeOrBuilder.java index b359c8d3..c70c3d12 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeOrBuilder.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeOrBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import com.android.aapt.Resources.XmlElementOrBuilder; import com.android.aapt.Resources.XmlNode.NodeCase; diff --git a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoPrintUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoPrintUtils.java similarity index 88% rename from src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoPrintUtils.java rename to src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoPrintUtils.java index ddcf045f..736559a2 100755 --- a/src/main/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoPrintUtils.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoPrintUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.collect.Ordering.explicit; import static java.util.Comparator.naturalOrder; @@ -40,7 +40,7 @@ public final class XmlProtoPrintUtils { public static String getValueAsString(Value value) { switch (value.getValueCase()) { case ITEM: - return getItemValueAsString(value.getItem()); + return getItemValueAsQuotedString(value.getItem()); case COMPOUND_VALUE: return getCompoundValueAsString(value.getCompoundValue()); default: @@ -57,15 +57,15 @@ public static String getCompoundValueAsString(CompoundValue compoundValue) { .collect(joining(", ", "{", "}")); case ARRAY: return compoundValue.getArray().getElementList().stream() - .map(element -> getItemValueAsString(element.getItem())) + .map(element -> getItemValueAsQuotedString(element.getItem())) .collect(joining(", ", "[", "]")); case PLURAL: return compoundValue.getPlural().getEntryList().stream() - .map(entry -> entry.getArity() + "=" + getItemValueAsString(entry.getItem())) + .map(entry -> entry.getArity() + "=" + getItemValueAsQuotedString(entry.getItem())) .collect(joining(", ", "{", "}")); case STYLE: return compoundValue.getStyle().getEntryList().stream() - .map(entry -> getItemValueAsString(entry.getItem())) + .map(entry -> getItemValueAsQuotedString(entry.getItem())) .collect(joining(", ", "[", "]")); case STYLEABLE: return compoundValue.getStyleable().getEntryList().stream() @@ -133,6 +133,20 @@ public static String getItemValueAsString(Item item) { } } + /** + * Same as {@link #getItemValueAsString(Item)} but escapes the strings and puts them in quotes. + */ + public static String getItemValueAsQuotedString(Item item) { + switch (item.getValueCase()) { + case STR: + case STYLED_STR: + case RAW_STR: + return "\"" + escapeString(getItemValueAsString(item)) + "\""; + default: + return getItemValueAsString(item); + } + } + /** * Returns the value of an {@link Primitive} as a string regardless of its type. * @@ -253,5 +267,22 @@ private enum Type { } } + private static String escapeString(String s) { + return s.replace("\\", "\\\\") // Do this one first since the ones below add more backslashes! + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + // Line tabulation + .replace("\u000B", "\\u000B") + // Form feed + .replace("\u000C", "\\u000C") + // Next line + .replace("\u0085", "\\u0085") + // Line separator + .replace("\u2028", "\\u2028") + // Paragraph separator + .replace("\u2029", "\\u2029"); + } + private XmlProtoPrintUtils() {} } diff --git a/src/main/java/com/android/tools/build/bundletool/version/BundleToolVersion.java b/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java similarity index 92% rename from src/main/java/com/android/tools/build/bundletool/version/BundleToolVersion.java rename to src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java index dc24c99d..2c692e3b 100755 --- a/src/main/java/com/android/tools/build/bundletool/version/BundleToolVersion.java +++ b/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.version; +package com.android.tools.build.bundletool.model.version; import com.android.bundle.Config.BundleConfig; import com.google.common.base.Strings; @@ -26,7 +26,7 @@ */ public final class BundleToolVersion { - private static final String CURRENT_VERSION = "0.7.2"; + private static final String CURRENT_VERSION = "0.8.0"; /** Returns the version of BundleTool being run. */ public static Version getCurrentVersion() { diff --git a/src/main/java/com/android/tools/build/bundletool/version/Version.java b/src/main/java/com/android/tools/build/bundletool/model/version/Version.java similarity index 93% rename from src/main/java/com/android/tools/build/bundletool/version/Version.java rename to src/main/java/com/android/tools/build/bundletool/model/version/Version.java index dfb9467f..44e7e930 100755 --- a/src/main/java/com/android/tools/build/bundletool/version/Version.java +++ b/src/main/java/com/android/tools/build/bundletool/model/version/Version.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.version; +package com.android.tools.build.bundletool.model.version; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.auto.value.AutoValue; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; +import com.google.errorprone.annotations.Immutable; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -29,7 +30,9 @@ *

Matches the pattern "..[-]", e.g. "1.1.0" or * "1.2.1-alpha03". */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class Version implements Comparable { private static final Pattern VERSION_REGEXP = @@ -97,7 +100,7 @@ public boolean isOlderThan(Version version) { } @Override - public String toString() { + 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 a3bfd509..5a182b89 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 @@ -21,13 +21,16 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.android.tools.build.bundletool.model.OptimizationDimension; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.version.Version; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; +import com.google.errorprone.annotations.Immutable; /** Optimizations that should be performed on the generated APKs. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ApkOptimizations { /** diff --git a/src/main/java/com/android/tools/build/bundletool/optimizations/OptimizationsMerger.java b/src/main/java/com/android/tools/build/bundletool/optimizations/OptimizationsMerger.java index db38757c..bcfcc977 100755 --- a/src/main/java/com/android/tools/build/bundletool/optimizations/OptimizationsMerger.java +++ b/src/main/java/com/android/tools/build/bundletool/optimizations/OptimizationsMerger.java @@ -22,9 +22,9 @@ import com.android.bundle.Config.Optimizations; import com.android.bundle.Config.SplitDimension; import com.android.tools.build.bundletool.model.OptimizationDimension; -import com.android.tools.build.bundletool.utils.EnumMapper; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.utils.EnumMapper; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.HashSet; diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitter.java index 2dd91b89..88c9ddcb 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitter.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitter.java @@ -25,9 +25,9 @@ import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.AbiTargeting; import com.android.bundle.Targeting.NativeDirectoryTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ModuleSplit; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/ApkGenerationConfiguration.java b/src/main/java/com/android/tools/build/bundletool/splitters/ApkGenerationConfiguration.java index 6b57ff98..f8bede1b 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/ApkGenerationConfiguration.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/ApkGenerationConfiguration.java @@ -20,9 +20,12 @@ import com.android.tools.build.bundletool.model.OptimizationDimension; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; /** Configuration to be passed to Module Splitters and Variant generators. */ +@Immutable @AutoValue +@AutoValue.CopyAnnotations public abstract class ApkGenerationConfiguration { public abstract ImmutableSet getOptimizationDimensions(); @@ -42,7 +45,9 @@ public abstract class ApkGenerationConfiguration { */ public abstract ImmutableSet getAbisForPlaceholderLibs(); - public static ApkGenerationConfiguration.Builder builder() { + public abstract Builder toBuilder(); + + public static Builder builder() { return new AutoValue_ApkGenerationConfiguration.Builder() .setForInstantAppVariants(false) .setEnableNativeLibraryCompressionSplitter(false) diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/AssetSlicesGenerator.java b/src/main/java/com/android/tools/build/bundletool/splitters/AssetSlicesGenerator.java new file mode 100755 index 00000000..5518102f --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/splitters/AssetSlicesGenerator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 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.splitters; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.ModuleSplit; +import com.google.common.collect.ImmutableList; + +/** + * Generates asset slices from remote asset modules. + * + *

Each asset in the module is inserted in at most one asset slice, according to its target. + */ +public class AssetSlicesGenerator { + + private final ImmutableList modules; + private final ApkGenerationConfiguration apkGenerationConfiguration; + + public AssetSlicesGenerator( + ImmutableList modules, ApkGenerationConfiguration apkGenerationConfiguration) { + this.modules = checkNotNull(modules); + this.apkGenerationConfiguration = checkNotNull(apkGenerationConfiguration); + } + + public ImmutableList generateAssetSlices() { + ImmutableList.Builder splits = ImmutableList.builder(); + + for (BundleModule module : modules) { + RemoteAssetModuleSplitter moduleSplitter = + new RemoteAssetModuleSplitter(module, apkGenerationConfiguration); + splits.addAll(moduleSplitter.splitModule()); + } + return splits.build(); + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/AssetsDimensionSplitterFactory.java b/src/main/java/com/android/tools/build/bundletool/splitters/AssetsDimensionSplitterFactory.java index fc84f1ec..1ee2ee7b 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/AssetsDimensionSplitterFactory.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/AssetsDimensionSplitterFactory.java @@ -30,7 +30,6 @@ import com.google.common.base.Function; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.protobuf.Message; @@ -79,14 +78,11 @@ public ImmutableCollection split(ModuleSplit split) { checkArgument( !hasTargeting.test(split.getApkTargeting()), "Split is already targeting the splitting dimension."); - if (!split.getAssetsConfig().isPresent()) { - return ImmutableList.of(split); - } - ImmutableList.Builder result = new Builder<>(); - Assets assetsConfig = split.getAssetsConfig().get(); - result.addAll(splitAssetsDirectories(assetsConfig, split)); - return result.build(); + return split + .getAssetsConfig() + .map(assetsConfig -> splitAssetsDirectories(assetsConfig, split)) + .orElse(ImmutableList.of(split)); } private ImmutableList splitAssetsDirectories(Assets assets, ModuleSplit split) { @@ -130,8 +126,7 @@ private ApkTargeting generateTargeting(ApkTargeting splitTargeting, T extraTarge private ImmutableList findEntriesInDirectories( Collection directories, ModuleSplit moduleSplit) { - return directories - .stream() + return directories.stream() .flatMap(directory -> moduleSplit.findEntriesInsideDirectory(directory.getPath())) .collect(toImmutableList()); } 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 ef049839..6d467128 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 @@ -16,16 +16,17 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.abiUniverse; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.densityUniverse; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.abiUniverse; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.densityUniverse; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Predicates.not; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.ApkTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.device.ApkMatcher; import com.android.tools.build.bundletool.mergers.D8DexMerger; import com.android.tools.build.bundletool.mergers.ModuleSplitsToShardMerger; import com.android.tools.build.bundletool.mergers.SameTargetingMerger; @@ -33,7 +34,9 @@ import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.OptimizationDimension; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.version.Version; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -41,6 +44,7 @@ import com.google.common.collect.Sets; import java.nio.file.Path; import java.util.Collection; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; @@ -56,15 +60,25 @@ public class BundleSharder { private final Version bundleVersion; private final ModuleSplitsToShardMerger merger; private final boolean generate64BitShards; + private final Optional deviceSpec; public BundleSharder(Path globalTempDir, Version bundleVersion) { this(globalTempDir, bundleVersion, /* generate64BitShards= */ true); } public BundleSharder(Path globalTempDir, Version bundleVersion, boolean generate64BitShards) { + this(globalTempDir, bundleVersion, generate64BitShards, Optional.empty()); + } + + public BundleSharder( + Path globalTempDir, + Version bundleVersion, + boolean generate64BitShards, + Optional deviceSpec) { this.bundleVersion = bundleVersion; this.merger = new ModuleSplitsToShardMerger(new D8DexMerger(), globalTempDir); this.generate64BitShards = generate64BitShards; + this.deviceSpec = deviceSpec; } /** @@ -104,6 +118,10 @@ public ImmutableList shardBundle( // Each sublist below represents a collection of splits targeting a specific device // configuration. ImmutableList> unfusedShards = groupSplitsToShards(moduleSplits); + checkState( + !deviceSpec.isPresent() || unfusedShards.size() == 1, + "Expected one set of splits when device spec is present but received %s.", + unfusedShards.size()); // Fuse each group of splits into a sharded APK. return merger.merge(unfusedShards, bundleMetadata); @@ -156,7 +174,7 @@ private ImmutableList generateSplits( private SplittingPipeline createNativeLibrariesSplittingPipeline( ImmutableSet shardingDimensions) { - return SplittingPipeline.create( + return new SplittingPipeline( shardingDimensions.contains(OptimizationDimension.ABI) ? ImmutableList.of(new AbiNativeLibrariesSplitter(generate64BitShards)) : ImmutableList.of()); @@ -164,15 +182,17 @@ private SplittingPipeline createNativeLibrariesSplittingPipeline( private SplittingPipeline createResourcesSplittingPipeline( ImmutableSet shardingDimensions) { - return SplittingPipeline.create( + return new SplittingPipeline( shardingDimensions.contains(OptimizationDimension.SCREEN_DENSITY) - ? ImmutableList.of(new ScreenDensityResourcesSplitter(bundleVersion)) + ? ImmutableList.of( + new ScreenDensityResourcesSplitter( + bundleVersion, /* pinResourceToMaster= */ Predicates.alwaysFalse())) : ImmutableList.of()); } private SplittingPipeline createApexImagesSplittingPipeline() { // We always split APEX image files by MultiAbi, regardless of OptimizationDimension. - return SplittingPipeline.create(ImmutableList.of(new AbiApexImagesSplitter())); + return new SplittingPipeline(ImmutableList.of(new AbiApexImagesSplitter())); } private ImmutableList> groupSplitsToShards( @@ -187,21 +207,12 @@ private ImmutableList> groupSplitsToShards( // * master splits: {m1-master, m2-master, ...} // * ABI splits: {m1-abi1, m1-abi2, ..., m2-abi1, m2-abi2, ...} // * density splits: {m1-density1, m1-density2, ..., m2-density1, m2-density2, ...} - Set abiSplits = subsetWithTargeting(splits, ApkTargeting::hasAbiTargeting); - Set densitySplits = + ImmutableSet abiSplits = + subsetWithTargeting(splits, ApkTargeting::hasAbiTargeting); + ImmutableSet densitySplits = subsetWithTargeting(splits, ApkTargeting::hasScreenDensityTargeting); - Set masterSplits = - Sets.difference(ImmutableSet.copyOf(splits), Sets.union(abiSplits, densitySplits)); + ImmutableSet masterSplits = getMasterSplits(splits); - checkState( - masterSplits.size() >= 1, - "Expecting at least one master split, got %s.", - masterSplits.size()); - checkState( - masterSplits - .stream() - .allMatch(split -> split.getApkTargeting().equals(ApkTargeting.getDefaultInstance())), - "Master splits are expected to have default targeting."); checkState( Sets.intersection(Sets.newHashSet(abiSplits), Sets.newHashSet(densitySplits)).isEmpty(), "No split is expected to have both ABI and screen density targeting."); @@ -270,14 +281,35 @@ private ImmutableList> groupSplitsToShardsForApex( .collect(toImmutableList()); } - private static ImmutableSet subsetWithTargeting( + private ImmutableSet subsetWithTargeting( ImmutableList splits, Predicate predicate) { - return splits - .stream() + return splits.stream() .filter(split -> predicate.test(split.getApkTargeting())) + .filter(split -> deviceSpec.map(spec -> splitMatchesDeviceSpec(split, spec)).orElse(true)) .collect(toImmutableSet()); } + /** Returns master splits, i.e splits without ABI or Density targeting. */ + private static ImmutableSet getMasterSplits(ImmutableList splits) { + ImmutableSet masterSplits = + splits.stream() + .filter( + split -> + !split.getApkTargeting().hasScreenDensityTargeting() + && !split.getApkTargeting().hasAbiTargeting()) + .collect(toImmutableSet()); + + checkState( + masterSplits.size() >= 1, + "Expecting at least one master split, got %s.", + masterSplits.size()); + checkState( + masterSplits.stream() + .allMatch(split -> split.getApkTargeting().equals(ApkTargeting.getDefaultInstance())), + "Master splits are expected to have default targeting."); + return masterSplits; + } + private static Collection> partitionByTargeting( Collection splits) { return Multimaps.index(splits, ModuleSplit::getApkTargeting).asMap().values(); @@ -299,4 +331,8 @@ private static boolean sameTargetedUniverse( .count(); return distinctNonEmptyUniverseCount <= 1; } + + private static boolean splitMatchesDeviceSpec(ModuleSplit moduleSplit, DeviceSpec deviceSpec) { + return new ApkMatcher(deviceSpec).matchesModuleSplitByTargeting(moduleSplit); + } } 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 85646452..6394bf66 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.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -28,10 +28,14 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -/** Splits the dex files in the module by their Compression. */ +/** Modifies a module by setting the compression of dex entries based on SDK version. */ public class DexCompressionSplitter implements ModuleSplitSplitter { - /** Generates {@link ModuleSplit} objects dividing the dex files by their compression. */ + /** + * Generates a single {@link ModuleSplit} setting the compression of dex entries. + * + *

Dex entries are only compressed for pre P devices. + */ @Override public ImmutableCollection split(ModuleSplit moduleSplit) { ImmutableSet dexEntries = 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 a95c347e..d0261ab8 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 @@ -17,10 +17,10 @@ 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.utils.TargetingProtoUtils.sdkVersionFrom; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionTargeting; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.variantTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; +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.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Targeting.VariantTargeting; diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitter.java index 73f229d7..35b615b0 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitter.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitter.java @@ -17,7 +17,8 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.BundleModule.RESOURCES_DIRECTORY; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.convertLocaleToLanguage; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.convertLocaleToLanguage; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.entries; import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.aapt.Resources.ConfigValue; @@ -26,14 +27,15 @@ import com.android.bundle.Targeting.LanguageTargeting; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ModuleSplit; -import com.android.tools.build.bundletool.utils.ResourcesUtils; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +import com.android.tools.build.bundletool.model.utils.ResourcesUtils; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import java.util.function.Predicate; /** * Splits the module resources by languages. @@ -52,6 +54,12 @@ */ public class LanguageResourcesSplitter extends SplitterForOneTargetingDimension { + private final Predicate pinResourceToMaster; + + public LanguageResourcesSplitter(Predicate pinResourceToMaster) { + this.pinResourceToMaster = pinResourceToMaster; + } + @Override public ImmutableCollection splitInternal(ModuleSplit split) { if (!split.getResourceTable().isPresent()) { @@ -95,9 +103,7 @@ public ImmutableCollection splitInternal(ModuleSplit split) { } private static boolean hasNonResourceEntries(ModuleSplit split) { - return split - .getEntries() - .stream() + return split.getEntries().stream() .anyMatch(entry -> !entry.getPath().startsWith(RESOURCES_DIRECTORY)); } @@ -106,21 +112,20 @@ private static ImmutableList getEntriesForSplit( ImmutableList entriesFromResourceTable = ModuleSplit.filterResourceEntries(inputEntries, resourceTable); if (language.isEmpty()) { // The split with no specific language targeting. - ImmutableList.Builder result = new Builder<>(); - result.addAll(entriesFromResourceTable); - // Add non-resource entries. - result.addAll( - inputEntries - .stream() - .filter(entry -> !entry.getPath().startsWith(RESOURCES_DIRECTORY)) - .collect(toImmutableList())); - return result.build(); + return ImmutableList.builder() + .addAll(entriesFromResourceTable) + // Add non-resource entries. + .addAll( + inputEntries.stream() + .filter(entry -> !entry.getPath().startsWith(RESOURCES_DIRECTORY)) + .collect(toImmutableList())) + .build(); } else { return entriesFromResourceTable; } } - private static ImmutableMap groupByLanguage( + private ImmutableMap groupByLanguage( ResourceTable table, boolean hasNonResourceEntries) { ImmutableSet languages = ResourcesUtils.getAllLanguages(table); @@ -130,39 +135,54 @@ private static ImmutableMap groupByLanguage( resourceTableByLanguage.put(language, filterByLanguage(table, language)); } - // If there are no resources with the default language (rare and not recommended) create an - // empty resource table. - // This semantic is desired here because we need a default language split to contain all non - // resource related entries. - if (!languages.contains("") && hasNonResourceEntries) { - resourceTableByLanguage.put("", ResourceTable.getDefaultInstance()); + // If there are no resources with the default language (rare and not recommended) create a + // resource table with pinned entries for master split or empty resource table if there are + // non resource related entries and no pinned entries. + if (!languages.contains("")) { + ResourceTable pinnedResources = + ResourcesUtils.filterResourceTable( + table, + /* removeEntryPredicate= */ pinResourceToMaster.negate(), + /* configValuesFilterFn= */ ResourceTableEntry::getEntry); + if (hasNonResourceEntries || entries(pinnedResources).count() > 0) { + resourceTableByLanguage.put("", pinnedResources); + } } return resourceTableByLanguage.build(); } - private static ResourceTable filterByLanguage(ResourceTable input, String language) { + private ResourceTable filterByLanguage(ResourceTable input, String language) { return ResourcesUtils.filterResourceTable( input, - /* removeTypePredicate= */ Predicates.alwaysFalse(), + /* removeEntryPredicate= */ language.isEmpty() + ? Predicates.alwaysFalse() + : pinResourceToMaster, /* configValuesFilterFn= */ entry -> filterEntryForLanguage(entry, language)); } /** - * Only leaves the language specific config values relevant for the given language. + * Only leaves the language specific config values relevant for the given language. For pinned + * resource to master splits, retains them fully if the target language is empty (default value). * * @param initialEntry the entry to be updated * @param targetLanguage the desired language to match - * @return the entry containing only config values specific to the given language + * @return the entry containing only config values specific to the given language or pinned + * resources to master splits if applicable. */ - private static Entry filterEntryForLanguage(Entry initialEntry, String targetLanguage) { + private Entry filterEntryForLanguage(ResourceTableEntry initialEntry, String targetLanguage) { + if (targetLanguage.isEmpty() && pinResourceToMaster.test(initialEntry)) { + return initialEntry.getEntry(); + } + Iterable filteredConfigValues = Iterables.filter( - initialEntry.getConfigValueList(), + initialEntry.getEntry().getConfigValueList(), configValue -> convertLocaleToLanguage(configValue.getConfig().getLocale()) .equals(targetLanguage)); return initialEntry + .getEntry() .toBuilder() .clearConfigValue() .addAllConfigValue(filteredConfigValues) 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 8ea9d870..dee797d8 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 @@ -16,8 +16,8 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.lPlusVariantTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_L_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.lPlusVariantTargeting; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_L_API_VERSION; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -26,7 +26,6 @@ import com.android.bundle.Targeting.SdkVersion; import com.android.bundle.Targeting.SdkVersionTargeting; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.mergers.SameTargetingMerger; import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.BundleModule; @@ -35,14 +34,19 @@ 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; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Predicates; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.protobuf.Int32Value; import java.util.Optional; +import java.util.function.Predicate; import javax.annotation.concurrent.GuardedBy; /** @@ -54,10 +58,12 @@ public class ModuleSplitter { private final BundleModule module; + private final ImmutableSet allModuleNames; private final SuffixManager suffixManager = new SuffixManager(); private final Version bundleVersion; private final ApkGenerationConfiguration apkGenerationConfiguration; private final VariantTargeting variantTargeting; + private final Predicate pinResourceToMaster; private final AbiPlaceholderInjector abiPlaceholderInjector; @@ -67,27 +73,33 @@ public class ModuleSplitter { module, bundleVersion, ApkGenerationConfiguration.getDefaultInstance(), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of(), + /* pinResourceToMaster= */ Predicates.alwaysFalse()); } public ModuleSplitter( BundleModule module, Version bundleVersion, ApkGenerationConfiguration apkGenerationConfiguration, - VariantTargeting variantTargeting) { + VariantTargeting variantTargeting, + ImmutableSet allModuleNames, + Predicate pinResourceToMaster) { this.module = checkNotNull(module); this.bundleVersion = checkNotNull(bundleVersion); this.apkGenerationConfiguration = checkNotNull(apkGenerationConfiguration); this.variantTargeting = checkNotNull(variantTargeting); this.abiPlaceholderInjector = new AbiPlaceholderInjector(apkGenerationConfiguration.getAbisForPlaceholderLibs()); + this.allModuleNames = allModuleNames; + this.pinResourceToMaster = pinResourceToMaster; } public ImmutableList splitModule() { if (apkGenerationConfiguration.isForInstantAppVariants()) { // Returns the list of module splits, ready for use as an instant app. return splitModuleInternal().stream() - .map(ModuleSplitter::makeInstantManifestChanges) + .map(this::makeInstantManifestChanges) .map(moduleSplit -> moduleSplit.toBuilder().setSplitType(SplitType.INSTANT).build()) .collect(toImmutableList()); } else { @@ -173,16 +185,23 @@ public ModuleSplit removeSplitName(ModuleSplit moduleSplit) { } /** - * Updates the split to insert instant app specific manifest changes. The targetSandboxVersion - * needs to be set to 2 and the minSdkVersion should be 21 or above. + * Updates the split to insert instant app specific manifest changes: + * + *

    + *
  • Sets the targetSandboxVersion to 2. + *
  • Sets the minSdkVersion to 21 if it the minSdkVersion is lower than 21. + *
  • Removes any known splits from the base manifest, which may contain on demand split + * information. + *
*/ - public static ModuleSplit makeInstantManifestChanges(ModuleSplit moduleSplit) { + public ModuleSplit makeInstantManifestChanges(ModuleSplit moduleSplit) { AndroidManifest manifest = moduleSplit.getAndroidManifest(); ManifestEditor editor = manifest.toEditor(); editor.setTargetSandboxVersion(2); if (manifest.getEffectiveMinSdkVersion() < 21) { editor.setMinSdkVersion(21); } + editor.removeUnknownSplitComponents(allModuleNames); return moduleSplit.toBuilder().setAndroidManifest(editor.save()).build(); } @@ -191,14 +210,14 @@ private SplittingPipeline createResourcesSplittingPipeline() { if (apkGenerationConfiguration .getOptimizationDimensions() .contains(OptimizationDimension.SCREEN_DENSITY)) { - resourceSplitters.add(new ScreenDensityResourcesSplitter(bundleVersion)); + resourceSplitters.add(new ScreenDensityResourcesSplitter(bundleVersion, pinResourceToMaster)); } if (apkGenerationConfiguration .getOptimizationDimensions() .contains(OptimizationDimension.LANGUAGE)) { - resourceSplitters.add(new LanguageResourcesSplitter()); + resourceSplitters.add(new LanguageResourcesSplitter(pinResourceToMaster)); } - return SplittingPipeline.create(resourceSplitters.build()); + return new SplittingPipeline(resourceSplitters.build()); } /** @@ -243,7 +262,7 @@ private SplittingPipeline createNativeLibrariesSplittingPipeline() { nativeSplitters.add( new AbiNativeLibrariesSplitter(apkGenerationConfiguration.getInclude64BitLibs())); } - return SplittingPipeline.create(nativeSplitters.build()); + return new SplittingPipeline(nativeSplitters.build()); } private SplittingPipeline createAssetsSplittingPipeline() { @@ -253,7 +272,7 @@ private SplittingPipeline createAssetsSplittingPipeline() { .contains(OptimizationDimension.LANGUAGE)) { assetsSplitters.add(LanguageAssetsSplitter.create()); } - return SplittingPipeline.create(assetsSplitters.build()); + return new SplittingPipeline(assetsSplitters.build()); } private SplittingPipeline createDexSplittingPipeline() { @@ -262,7 +281,7 @@ private SplittingPipeline createDexSplittingPipeline() { dexSplitters.add(new DexCompressionSplitter()); } - return SplittingPipeline.create(dexSplitters.build()); + return new SplittingPipeline(dexSplitters.build()); } private static boolean targetsOnlyPreL(BundleModule module) { diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitter.java index e5bbbf10..7e17fb39 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitter.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitter.java @@ -17,7 +17,7 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.ManifestMutator.withExtractNativeLibs; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_API_VERSION; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -32,7 +32,7 @@ import com.google.common.collect.Iterables; import java.util.List; -/** Splits the native libraries in the module by their Compression. */ +/** Modifies a module by setting the compression of of native libraries based on SDK version. */ public final class NativeLibrariesCompressionSplitter implements ModuleSplitSplitter { private final ApkGenerationConfiguration apkGenerationConfiguration; @@ -45,7 +45,12 @@ public final class NativeLibrariesCompressionSplitter implements ModuleSplitSpli public NativeLibrariesCompressionSplitter(ApkGenerationConfiguration apkGenerationConfiguration) { this.apkGenerationConfiguration = apkGenerationConfiguration; } - /** Generates {@link ModuleSplit} objects dividing the native libraries by their compression. */ + + /** + * Generates a single {@link ModuleSplit} setting the compression on native libraries. + * + *

Native libraries are only compressed for non instant apps on pre M devices. + */ @Override public ImmutableCollection split(ModuleSplit moduleSplit) { checkState( diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGenerator.java b/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGenerator.java index 5d9de8a1..737730ff 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGenerator.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGenerator.java @@ -16,10 +16,10 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionFrom; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionTargeting; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.variantTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; +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_M_API_VERSION; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.model.BundleModule; diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/RemoteAssetModuleSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/RemoteAssetModuleSplitter.java new file mode 100755 index 00000000..21c777a9 --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/splitters/RemoteAssetModuleSplitter.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 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.splitters; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.ModuleSplit; +import com.android.tools.build.bundletool.model.OptimizationDimension; +import com.google.common.collect.ImmutableList; + +/** Splits a remote asset module into asset slices, each targeting a specific configuration. */ +public class RemoteAssetModuleSplitter { + private final BundleModule module; + private final ApkGenerationConfiguration apkGenerationConfiguration; + + public RemoteAssetModuleSplitter( + BundleModule module, ApkGenerationConfiguration apkGenerationConfiguration) { + this.module = checkNotNull(module); + this.apkGenerationConfiguration = checkNotNull(apkGenerationConfiguration); + } + + public ImmutableList splitModule() { + ImmutableList.Builder splits = ImmutableList.builder(); + + // Assets splits. + SplittingPipeline assetsPipeline = createAssetsSplittingPipeline(); + splits.addAll(assetsPipeline.split(ModuleSplit.fromAssetBundleModule(module))); + + return splits.build(); + } + + private SplittingPipeline createAssetsSplittingPipeline() { + ImmutableList.Builder assetsSplitters = ImmutableList.builder(); + if (apkGenerationConfiguration + .getOptimizationDimensions() + .contains(OptimizationDimension.LANGUAGE)) { + assetsSplitters.add(LanguageAssetsSplitter.create()); + } + return new SplittingPipeline(assetsSplitters.build()); + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitter.java index 30972930..6d5458d8 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitter.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitter.java @@ -17,8 +17,8 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.ManifestMutator.withSplitsRequired; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.MIPMAP_TYPE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MIPMAP_TYPE; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -35,9 +35,10 @@ import com.android.bundle.Targeting.ScreenDensityTargeting; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ResourceId; -import com.android.tools.build.bundletool.targeting.ScreenDensitySelector; -import com.android.tools.build.bundletool.utils.ResourcesUtils; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +import com.android.tools.build.bundletool.model.targeting.ScreenDensitySelector; +import com.android.tools.build.bundletool.model.utils.ResourcesUtils; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; @@ -49,6 +50,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; /** Splits module resources by screen density. */ public class ScreenDensityResourcesSplitter extends SplitterForOneTargetingDimension { @@ -65,15 +67,20 @@ public class ScreenDensityResourcesSplitter extends SplitterForOneTargetingDimen private final ImmutableSet densityBuckets; private final Version bundleVersion; + private final Predicate pinResourceToMaster; - public ScreenDensityResourcesSplitter(Version bundleVersion) { - this(DEFAULT_DENSITY_BUCKETS, bundleVersion); + public ScreenDensityResourcesSplitter( + Version bundleVersion, Predicate pinResourceToMaster) { + this(DEFAULT_DENSITY_BUCKETS, bundleVersion, pinResourceToMaster); } public ScreenDensityResourcesSplitter( - ImmutableSet densityBuckets, Version bundleVersion) { + ImmutableSet densityBuckets, + Version bundleVersion, + Predicate pinResourceToMaster) { this.densityBuckets = densityBuckets; this.bundleVersion = bundleVersion; + this.pinResourceToMaster = pinResourceToMaster; } @Override @@ -190,7 +197,8 @@ private ResourceTable filterResourceTableForDensity(ResourceTable input, Density return ResourcesUtils.filterResourceTable( input, // Put mipmaps into the master split. - /* removeTypePredicate= */ type -> type.getName().equals(MIPMAP_TYPE), + /* removeEntryPredicate= */ pinResourceToMaster.or( + resourceTableEntry -> resourceTableEntry.getType().getName().equals(MIPMAP_TYPE)), /* configValuesFilterFn= */ entry -> filterEntryForDensity(entry, density)); } @@ -200,11 +208,12 @@ private ResourceTable filterResourceTableForDensity(ResourceTable input, Density *

As any other resource qualifiers can be requested when delivering resources, the algorithm * chooses the best match only within group of resources differing by density only. * - * @param initialEntry the entry to be updated + * @param tableEntry the entry to be updated * @param targetDensity the desired density to match * @return the entry with the best matching density config values. */ - private Entry filterEntryForDensity(Entry initialEntry, DensityAlias targetDensity) { + private Entry filterEntryForDensity(ResourceTableEntry tableEntry, DensityAlias targetDensity) { + Entry initialEntry = tableEntry.getEntry(); // Groups together configs that only differ on density. Map> configValuesByConfiguration = initialEntry.getConfigValueList().stream() 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 4e9eef5c..1fc5ebd3 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 @@ -16,10 +16,11 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.sdkVersionFrom; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.sdkVersionFrom; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; +import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.SdkVersionTargeting; import com.android.bundle.Targeting.VariantTargeting; @@ -27,10 +28,11 @@ import com.android.tools.build.bundletool.model.BundleModule; 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.Version; import com.android.tools.build.bundletool.optimizations.ApkOptimizations; -import com.android.tools.build.bundletool.version.Version; import com.google.common.collect.ImmutableList; import java.nio.file.Path; +import java.util.Optional; /** * Generates APKs sharded by ABI and screen density. @@ -60,12 +62,15 @@ public ImmutableList generateSplits( ImmutableList modules, BundleMetadata bundleMetadata, ApkOptimizations apkOptimizations) { + return generateSplitsInternal(modules, bundleMetadata, apkOptimizations, Optional.empty()); + } - BundleSharder bundleSharder = new BundleSharder(tempDir, bundleVersion, generate64BitShards); - ImmutableList shardedApks = - bundleSharder.shardBundle(modules, apkOptimizations.getSplitDimensions(), bundleMetadata); - - return setVariantTargetingAndSplitType(shardedApks, splitType); + public ImmutableList generateSystemSplits( + ImmutableList modules, + BundleMetadata bundleMetadata, + ApkOptimizations apkOptimizations, + Optional deviceSpec) { + return generateSplitsInternal(modules, bundleMetadata, apkOptimizations, deviceSpec); } public ImmutableList generateApexSplits(ImmutableList modules) { @@ -77,6 +82,19 @@ public ImmutableList generateApexSplits(ImmutableList return setVariantTargetingAndSplitType(shardedApexApks, splitType); } + private ImmutableList generateSplitsInternal( + ImmutableList modules, + BundleMetadata bundleMetadata, + ApkOptimizations apkOptimizations, + Optional deviceSpec) { + BundleSharder bundleSharder = + new BundleSharder(tempDir, bundleVersion, generate64BitShards, deviceSpec); + ImmutableList shardedApks = + bundleSharder.shardBundle(modules, apkOptimizations.getSplitDimensions(), bundleMetadata); + + return setVariantTargetingAndSplitType(shardedApks, splitType); + } + private static ImmutableList setVariantTargetingAndSplitType( ImmutableList standaloneApks, SplitType splitType) { return standaloneApks.stream() diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/SplitApksGenerator.java b/src/main/java/com/android/tools/build/bundletool/splitters/SplitApksGenerator.java index 1e04e764..a8481ada 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/SplitApksGenerator.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/SplitApksGenerator.java @@ -16,16 +16,19 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.targeting.TargetingUtils.generateAllVariantTargetings; +import static com.android.tools.build.bundletool.model.targeting.TargetingUtils.generateAllVariantTargetings; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleSplit; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import java.util.function.Predicate; /** Generates split APKs. */ public final class SplitApksGenerator { @@ -33,14 +36,17 @@ public final class SplitApksGenerator { private final ImmutableList modules; private final ApkGenerationConfiguration apkGenerationConfiguration; private final Version bundleVersion; + private final Predicate masterResourcesPredicate; public SplitApksGenerator( ImmutableList modules, Version bundleVersion, - ApkGenerationConfiguration apkGenerationConfiguration) { + ApkGenerationConfiguration apkGenerationConfiguration, + Predicate masterResourcesPredicate) { this.modules = checkNotNull(modules); this.bundleVersion = checkNotNull(bundleVersion); this.apkGenerationConfiguration = checkNotNull(apkGenerationConfiguration); + this.masterResourcesPredicate = masterResourcesPredicate; } public ImmutableList generateSplits() { @@ -61,11 +67,19 @@ private ImmutableSet generateVariants() { } private ImmutableList generateSplitApks(VariantTargeting variantTargeting) { + ImmutableSet allModuleNames = + modules.stream().map(module -> module.getName().getName()).collect(toImmutableSet()); ImmutableList.Builder splits = ImmutableList.builder(); for (BundleModule module : modules) { ModuleSplitter moduleSplitter = - new ModuleSplitter(module, bundleVersion, apkGenerationConfiguration, variantTargeting); + new ModuleSplitter( + module, + bundleVersion, + apkGenerationConfiguration, + variantTargeting, + allModuleNames, + masterResourcesPredicate); splits.addAll(moduleSplitter.splitModule()); } return splits.build(); diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/SplittingPipeline.java b/src/main/java/com/android/tools/build/bundletool/splitters/SplittingPipeline.java index 6cc0cc29..d09ca3ab 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/SplittingPipeline.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/SplittingPipeline.java @@ -19,20 +19,26 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.tools.build.bundletool.model.ModuleSplit; -import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import java.util.Collection; /** Pipeline chaining the execution of module splitters. */ -@AutoValue -public abstract class SplittingPipeline { +public final class SplittingPipeline { - public abstract ImmutableList getSplitters(); + private final ImmutableList splitters; + + SplittingPipeline(ImmutableList splitters) { + this.splitters = splitters; + } + + public ImmutableList getSplitters() { + return splitters; + } public ImmutableCollection split(ModuleSplit split) { ImmutableList splits = ImmutableList.of(split); - for (ModuleSplitSplitter splitter : getSplitters()) { + for (ModuleSplitSplitter splitter : splitters) { splits = splits .stream() @@ -42,8 +48,4 @@ public ImmutableCollection split(ModuleSplit split) { } return splits; } - - public static SplittingPipeline create(ImmutableList splitters) { - return new AutoValue_SplittingPipeline(splitters); - } } diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/VariantGenerator.java b/src/main/java/com/android/tools/build/bundletool/splitters/VariantGenerator.java index 10a0ba97..d84533f6 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/VariantGenerator.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/VariantGenerator.java @@ -16,12 +16,12 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.utils.TargetingProtoUtils.lPlusVariantTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_L_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.lPlusVariantTargeting; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_L_API_VERSION; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/AbiParityValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/AbiParityValidator.java index 17318524..9187765d 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/AbiParityValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/AbiParityValidator.java @@ -19,10 +19,10 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Targeting.Abi.AbiAlias; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; import com.android.tools.build.bundletool.model.BundleModule; -import com.android.tools.build.bundletool.targeting.TargetedDirectorySegment; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.targeting.TargetedDirectorySegment; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Set; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/AndroidManifestValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/AndroidManifestValidator.java index 77d86008..c42ef032 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/AndroidManifestValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/AndroidManifestValidator.java @@ -22,19 +22,19 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestDuplicateAttributeException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestFusingException.BaseModuleExcludedFromFusingException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestFusingException.ModuleFusingConfigurationMissingException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MaxSdkInvalidException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MaxSdkLessThanMinInstantSdk; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MinSdkGreaterThanMaxSdkException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MinSdkInvalidException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestVersionCodeConflictException; 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.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestDuplicateAttributeException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestFusingException.BaseModuleExcludedFromFusingException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestFusingException.ModuleFusingConfigurationMissingException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MaxSdkInvalidException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MaxSdkLessThanMinInstantSdk; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MinSdkGreaterThanMaxSdkException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MinSdkInvalidException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestVersionCodeConflictException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Optional; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/ApexBundleValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/ApexBundleValidator.java index 38df8092..4bfbd255 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/ApexBundleValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/ApexBundleValidator.java @@ -25,12 +25,14 @@ import static com.android.tools.build.bundletool.model.BundleModule.APEX_MANIFEST_PATH; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import com.android.bundle.Files.ApexImages; import com.android.bundle.Files.TargetedApexImage; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -39,10 +41,15 @@ /** Validates an APEX bundle. */ public class ApexBundleValidator extends SubValidator { - private static final ImmutableSet> REQUIRED_SINGLETON_ABIS = - ImmutableList.of(X86_64, X86, ARMEABI_V7A, ARM64_V8A).stream() - .map(ImmutableSet::of) - .collect(toImmutableSet()); + // The bundle must contain a system image for at least one of each of these sets. + private static final ImmutableSet>> REQUIRED_ONE_OF_ABI_SETS = + ImmutableSet.of( + // These 32-bit ABIs must be present. + ImmutableSet.of(ImmutableSet.of(X86)), + ImmutableSet.of(ImmutableSet.of(ARMEABI_V7A)), + // These 64-bit ABIs must be present on their own or with the corresponding 32-bit ABI. + ImmutableSet.of(ImmutableSet.of(X86_64), ImmutableSet.of(X86_64, X86)), + ImmutableSet.of(ImmutableSet.of(ARM64_V8A), ImmutableSet.of(ARM64_V8A, ARMEABI_V7A))); @Override public void validateAllModules(ImmutableList modules) { @@ -67,7 +74,7 @@ public void validateAllModules(ImmutableList modules) { @Override public void validateModule(BundleModule module) { - if (!module.getApexConfig().isPresent()) { + if (module.findEntriesUnderPath(APEX_DIRECTORY).count() == 0) { return; } @@ -92,25 +99,6 @@ public void validateModule(BundleModule module) { } ImmutableSet apexImages = apexImagesBuilder.build(); - ImmutableSet targetedImages = - module.getApexConfig().get().getImageList().stream() - .map(TargetedApexImage::getPath) - .collect(toImmutableSet()); - ImmutableSet untargetedImages = - Sets.difference(apexImages, targetedImages).immutableCopy(); - if (!untargetedImages.isEmpty()) { - throw ValidationException.builder() - .withMessage("Found APEX image files that are not targeted: %s", untargetedImages) - .build(); - } - ImmutableSet missingTargetedImages = - Sets.difference(targetedImages, apexImages).immutableCopy(); - if (!missingTargetedImages.isEmpty()) { - throw ValidationException.builder() - .withMessage("Targeted APEX image files are missing: %s", missingTargetedImages) - .build(); - } - ImmutableSet> allAbiNameSets = apexFileNamesBuilder.build().stream() .map(ApexBundleValidator::abiNamesFromFile) @@ -123,13 +111,16 @@ public void validateModule(BundleModule module) { .build(); } - if (!allAbiNameSets.containsAll(REQUIRED_SINGLETON_ABIS)) { + if (REQUIRED_ONE_OF_ABI_SETS.stream() + .anyMatch(one_of -> one_of.stream().noneMatch(allAbiNameSets::contains))) { throw ValidationException.builder() .withMessage( - "APEX bundle must contain all these singleton architectures: ." - + REQUIRED_SINGLETON_ABIS) + "APEX bundle must contain one of %s.", + Joiner.on(" and one of ").join(REQUIRED_ONE_OF_ABI_SETS)) .build(); } + + module.getApexConfig().ifPresent(targeting -> validateTargeting(apexImages, targeting)); } private static ImmutableSet abiNamesFromFile(String fileName) { @@ -143,4 +134,25 @@ private static ImmutableSet abiNamesFromFile(String fileName) { .map(Optional::get) .collect(toImmutableSet()); } + + private static void validateTargeting(ImmutableSet allImages, ApexImages targeting) { + ImmutableSet targetedImages = + targeting.getImageList().stream().map(TargetedApexImage::getPath).collect(toImmutableSet()); + + ImmutableSet untargetedImages = + Sets.difference(allImages, targetedImages).immutableCopy(); + if (!untargetedImages.isEmpty()) { + throw ValidationException.builder() + .withMessage("Found APEX image files that are not targeted: %s", untargetedImages) + .build(); + } + + ImmutableSet missingTargetedImages = + Sets.difference(targetedImages, allImages).immutableCopy(); + if (!missingTargetedImages.isEmpty()) { + throw ValidationException.builder() + .withMessage("Targeted APEX image files are missing: %s", missingTargetedImages) + .build(); + } + } } diff --git a/src/main/java/com/android/tools/build/bundletool/validation/AppBundleValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/AppBundleValidator.java index 225987a1..70ee37db 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/AppBundleValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/AppBundleValidator.java @@ -16,8 +16,8 @@ package com.android.tools.build.bundletool.validation; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AppBundle; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import java.util.zip.ZipFile; @@ -38,6 +38,7 @@ public class AppBundleValidator { ImmutableList.of( // Fundamental file validations first. new BundleFilesValidator(), + new ModuleNamesValidator(), new AndroidManifestValidator(), new BundleConfigValidator(), // More specific file validations. diff --git a/src/main/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidator.java index 74dbcaba..47a2e194 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidator.java @@ -20,9 +20,9 @@ import com.android.bundle.Files.Assets; import com.android.bundle.Files.TargetedAssetsDirectory; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; /** Validates targeting of assets. */ public class AssetsTargetingValidator extends SubValidator { diff --git a/src/main/java/com/android/tools/build/bundletool/validation/BundleConfigValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/BundleConfigValidator.java index 78ded565..7bb4a2f0 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/BundleConfigValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/BundleConfigValidator.java @@ -15,18 +15,27 @@ */ package com.android.tools.build.bundletool.validation; +import static com.google.common.collect.ImmutableSet.toImmutableSet; + import com.android.bundle.Config.BundleConfig; import com.android.bundle.Config.Compression; import com.android.bundle.Config.Optimizations; import com.android.bundle.Config.SplitDimension; import com.android.bundle.Config.SplitDimension.Value; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AppBundle; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.ResourceId; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +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.version.BundleToolVersion; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.util.List; +import java.util.Optional; /** Validator of the BundleConfig. */ public final class BundleConfigValidator extends SubValidator { @@ -44,6 +53,7 @@ public void validateBundle(AppBundle bundle) { validateVersion(bundleConfig); validateCompression(bundleConfig.getCompression()); validateOptimizations(bundleConfig.getOptimizations()); + validateMasterResources(bundleConfig, bundle); } private void validateCompression(Compression compression) { @@ -102,4 +112,33 @@ private void validateVersion(BundleConfig bundleConfig) { .build(); } } + + private void validateMasterResources(BundleConfig bundleConfig, AppBundle bundle) { + ImmutableSet resourcesToBePinned = + ImmutableSet.copyOf(bundleConfig.getMasterResources().getResourceIdsList()); + if (resourcesToBePinned.isEmpty()) { + return; + } + + ImmutableSet allResourceIds = + bundle.getFeatureModules().values().stream() + .map(BundleModule::getResourceTable) + .filter(Optional::isPresent) + .map(Optional::get) + .flatMap(resourceTable -> ResourcesUtils.entries(resourceTable)) + .map(ResourceTableEntry::getResourceId) + .map(ResourceId::getFullResourceId) + .collect(toImmutableSet()); + + SetView undefinedResources = Sets.difference(resourcesToBePinned, allResourceIds); + + if (!undefinedResources.isEmpty()) { + throw ValidationException.builder() + .withMessage( + "Error in BundleConfig. The Master Resources list contains resource IDs not defined " + + "in any module. For example: 0x%08x", + undefinedResources.iterator().next()) + .build(); + } + } } diff --git a/src/main/java/com/android/tools/build/bundletool/validation/BundleFilesValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/BundleFilesValidator.java index 891a5666..6a4ffccb 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/BundleFilesValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/BundleFilesValidator.java @@ -24,23 +24,23 @@ import static com.android.tools.build.bundletool.model.BundleModule.MANIFEST_DIRECTORY; import static com.android.tools.build.bundletool.model.BundleModule.MANIFEST_FILENAME; import static com.android.tools.build.bundletool.model.BundleModule.RESOURCES_DIRECTORY; -import static com.android.tools.build.bundletool.model.BundleModule.RESOURCES_PROTO_PATH; import static com.android.tools.build.bundletool.model.BundleModule.ROOT_DIRECTORY; 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.tools.build.bundletool.exceptions.BundleFileTypesException.FileUsesReservedNameException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.FilesInResourceDirectoryRootException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidApexImagePathException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidFileExtensionInDirectoryException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidFileNameInDirectoryException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidNativeArchitectureNameException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidNativeLibraryPathException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.UnknownFileOrDirectoryFoundInModuleException; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; +import com.android.tools.build.bundletool.model.BundleModule.SpecialModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.FileUsesReservedNameException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.FilesInResourceDirectoryRootException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidApexImagePathException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidFileExtensionInDirectoryException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidFileNameInDirectoryException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidNativeArchitectureNameException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidNativeLibraryPathException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.UnknownFileOrDirectoryFoundInModuleException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Optional; @@ -64,7 +64,7 @@ public class BundleFilesValidator extends SubValidator { ZipPath.create("resources.arsc"), // Special files in the proto APKs (created by the 'split-module' command). ZipPath.create(MANIFEST_FILENAME), - RESOURCES_PROTO_PATH); + SpecialModuleEntry.RESOURCE_TABLE.getPath()); @Override public void validateModuleFile(ZipPath file) { diff --git a/src/main/java/com/android/tools/build/bundletool/validation/BundleModulesValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/BundleModulesValidator.java index 7291a8cc..be4cbf74 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/BundleModulesValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/BundleModulesValidator.java @@ -16,27 +16,19 @@ package com.android.tools.build.bundletool.validation; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileHasExtension; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileNamesAreUnique; import static com.google.common.base.Predicates.not; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.io.MoreFiles.getNameWithoutExtension; import com.android.bundle.Config.BundleConfig; import com.android.bundle.Config.Bundletool; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ModuleZipEntry; -import com.android.tools.build.bundletool.utils.ZipUtils; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import com.google.common.io.Closer; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -62,6 +54,7 @@ public class BundleModulesValidator { ImmutableList.of( // Fundamental file validations first. new BundleFilesValidator(), + new ModuleNamesValidator(), new AndroidManifestValidator(), // More specific file validations. new EntryClashValidator(), @@ -79,48 +72,46 @@ public class BundleModulesValidator { Bundletool.newBuilder().setVersion(BundleToolVersion.getCurrentVersion().toString())) .build(); - public void validate(ImmutableList modulePaths) { - // Name of the module zip file is name of the module it contains. Therefore filenames must - // be unique. - checkFileNamesAreUnique("Modules", modulePaths); + public ImmutableList validate(ImmutableList moduleZips) { + for (ZipFile moduleZip : moduleZips) { + new ValidatorRunner(MODULE_FILE_SUB_VALIDATORS).validateModuleZipFile(moduleZip); + } - final Map zipFileByPath = new HashMap<>(); - try (Closer closer = Closer.create()) { - for (Path modulePath : modulePaths) { - checkFileHasExtension("Module", modulePath, ".zip"); - ZipFile moduleZip = closer.register(ZipUtils.openZipFile(modulePath)); - zipFileByPath.put(modulePath, moduleZip); - new ValidatorRunner(MODULE_FILE_SUB_VALIDATORS).validateModuleZipFile(moduleZip); - } + ImmutableList modules = + moduleZips.stream().map(this::toBundleModule).collect(toImmutableList()); - ImmutableList modules = - zipFileByPath.entrySet().stream() - .map(entry -> toBundleModule(entry.getKey(), entry.getValue())) - .collect(toImmutableList()); + new ValidatorRunner(MODULES_SUB_VALIDATORS).validateBundleModules(modules); - new ValidatorRunner(MODULES_SUB_VALIDATORS).validateBundleModules(modules); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return modules; } - private BundleModule toBundleModule(Path modulePath, ZipFile moduleZipFile) { - // Validates the module name. - BundleModuleName moduleName = BundleModuleName.create(getNameWithoutExtension(modulePath)); - + private BundleModule toBundleModule(ZipFile moduleZipFile) { + BundleModule bundleModule; try { - return BundleModule.builder() - .setName(moduleName) - .setBundleConfig(EMPTY_CONFIG_WITH_CURRENT_VERSION) - .addEntries( - moduleZipFile.stream() - .filter(not(ZipEntry::isDirectory)) - .map(zipEntry -> ModuleZipEntry.fromModuleZipEntry(zipEntry, moduleZipFile)) - .collect(toImmutableList())) - .build(); + bundleModule = + BundleModule.builder() + // Assigning a temporary name because the real one will be extracted from the + // manifest, but this requires the BundleModule to be built. + .setName(BundleModuleName.create("TEMPORARY_MODULE_NAME")) + .setBundleConfig(EMPTY_CONFIG_WITH_CURRENT_VERSION) + .addEntries( + moduleZipFile.stream() + .filter(not(ZipEntry::isDirectory)) + .map(zipEntry -> ModuleZipEntry.fromModuleZipEntry(zipEntry, moduleZipFile)) + .collect(toImmutableList())) + .build(); } catch (IOException e) { throw new UncheckedIOException( - String.format("Error reading module zip file '%s'.", modulePath), e); + String.format("Error reading module zip file '%s'.", moduleZipFile.getName()), e); } + + BundleModuleName actualModuleName = + BundleModuleName.create( + bundleModule + .getAndroidManifest() + .getSplitId() + .orElse(BundleModuleName.BASE_MODULE_NAME)); + + return bundleModule.toBuilder().setName(actualModuleName).build(); } } diff --git a/src/main/java/com/android/tools/build/bundletool/validation/BundleZipValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/BundleZipValidator.java index c003480e..a21ec7e9 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/BundleZipValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/BundleZipValidator.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.validation; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.DirectoryInBundleException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.DirectoryInBundleException; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/DexFilesValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/DexFilesValidator.java index e62581a6..a108f154 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/DexFilesValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/DexFilesValidator.java @@ -20,8 +20,8 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import java.util.Comparator; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/EntryClashValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/EntryClashValidator.java index 573f1742..7ea56601 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/EntryClashValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/EntryClashValidator.java @@ -18,10 +18,10 @@ import static com.android.tools.build.bundletool.model.BundleModule.DEX_DIRECTORY; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableList; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidator.java index d94898d3..8c9e0925 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidator.java @@ -19,11 +19,11 @@ import static com.google.common.base.Predicates.not; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.MandatoryBundleFileMissingException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.MandatoryModuleFileMissingException; import com.android.tools.build.bundletool.model.AppBundle; -import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModule.SpecialModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.MandatoryBundleFileMissingException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.MandatoryModuleFileMissingException; import com.google.common.collect.ImmutableSet; import com.google.common.io.Files; import java.util.Collections; @@ -47,8 +47,7 @@ public void validateModuleZipFile(ZipFile moduleFile) { @Override public void validateBundleZipFile(ZipFile bundleFile) { ImmutableSet moduleDirectories = - Collections.list(bundleFile.entries()) - .stream() + Collections.list(bundleFile.entries()).stream() .map(ZipEntry::getName) .map(ZipPath::create) .filter(entryPath -> entryPath.getNameCount() > 1) @@ -73,10 +72,12 @@ private static void checkBundleHasBundleConfig(ZipFile bundleFile) { private static void checkModuleHasAndroidManifest( ZipFile zipFile, ZipPath moduleBaseDir, String moduleName) { - ZipPath moduleManifestPath = moduleBaseDir.resolve(BundleModule.MANIFEST_PATH); + ZipPath moduleManifestPath = + moduleBaseDir.resolve(SpecialModuleEntry.ANDROID_MANIFEST.getPath()); if (zipFile.getEntry(moduleManifestPath.toString()) == null) { - throw new MandatoryModuleFileMissingException(moduleName, BundleModule.MANIFEST_PATH); + throw new MandatoryModuleFileMissingException( + moduleName, SpecialModuleEntry.ANDROID_MANIFEST.getPath()); } } } 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 dd14d660..4b070b23 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 @@ -19,10 +19,10 @@ import static com.android.tools.build.bundletool.model.BundleModuleName.BASE_MODULE_NAME; import static com.google.common.base.Preconditions.checkArgument; -import com.android.tools.build.bundletool.exceptions.ValidationException; 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.exceptions.ValidationException; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -33,7 +33,6 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map.Entry; -import java.util.Optional; import java.util.Set; /** @@ -52,7 +51,6 @@ public class ModuleDependencyValidator extends SubValidator { @Override public void validateAllModules(ImmutableList modules) { checkHasBaseModule(modules); - checkSplitIds(modules); Multimap moduleDependenciesMap = buildAdjacencyMap(modules); ImmutableMap modulesByName = @@ -114,37 +112,6 @@ private static void checkHasBaseModule(ImmutableList modules) { } } - private static void checkSplitIds(ImmutableList modules) { - // This is rather a sanity check. Currently the bundletool ignores and/or rewrites the - // attribute, but mismatch should not happen. - - for (BundleModule module : modules) { - String moduleName = module.getName().getName(); - Optional splitIdFromManifest = module.getAndroidManifest().getSplitId(); - - if (module.isBaseModule()) { - // Follows the Split APK conventions - the base split has empty split ID. - if (splitIdFromManifest.isPresent()) { - throw ValidationException.builder() - .withMessage( - "The base module should not declare split ID in the manifest, but it is set to " - + "'%s'.", - splitIdFromManifest.get()) - .build(); - } - } else { - if (splitIdFromManifest.isPresent() && !moduleName.equals(splitIdFromManifest.get())) { - throw ValidationException.builder() - .withMessage( - "Module '%s' declares in its manifest that the split ID is '%s'. It needs to be" - + " either absent or equal to the module name.", - moduleName, splitIdFromManifest.get()) - .build(); - } - } - } - } - private static void checkReferencedModulesExist(Multimap moduleDependenciesMap) { for (String referencedModule : moduleDependenciesMap.values()) { if (!moduleDependenciesMap.containsKey(referencedModule)) { diff --git a/src/main/java/com/android/tools/build/bundletool/validation/ModuleNamesValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/ModuleNamesValidator.java new file mode 100755 index 00000000..a540559e --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/validation/ModuleNamesValidator.java @@ -0,0 +1,91 @@ +/* + * 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.validation; + +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.ImmutableList; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** + * Validator ensuring that all modules have distinct module names and splitId are correctly set. + * + *

This validator is used to validate both App Bundle files, as well as individual bundle module + * zip files used to build an App Bundle. + */ +final class ModuleNamesValidator extends SubValidator { + + @Override + public void validateAllModules(ImmutableList modules) { + Set moduleNames = new HashSet<>(); + + for (BundleModule module : modules) { + Optional splitId = module.getAndroidManifest().getSplitId(); + BundleModuleName moduleName = module.getName(); + + if (moduleName.getName().equals(BundleModuleName.BASE_MODULE_NAME)) { + if (splitId.isPresent()) { + throw new ValidationException( + "The base module should not have the 'split' attribute set in the " + + "AndroidManifest.xml"); + } + } else { + if (splitId.isPresent()) { + if (!splitId.get().equals(moduleName.getName())) { + throw ValidationException.builder() + .withMessage( + "The 'split' attribute in the AndroidManifest.xml of modules must be the name " + + "of the module, but has the value '%s' in module '%s'", + splitId.get(), moduleName) + .build(); + } + } else { + throw ValidationException.builder() + .withMessage( + "No 'split' attribute found in the AndroidManifest.xml of module '%s'.", + moduleName) + .build(); + } + } + + boolean alreadyPresent = !moduleNames.add(moduleName); + if (alreadyPresent) { + if (splitId.isPresent()) { + throw ValidationException.builder() + .withMessage( + "More than one module have the 'split' attribute set to '%s' in the " + + "AndroidManifest.xml.", + splitId.get()) + .build(); + } else { + throw new ValidationException( + "More than one module was found without the 'split' attribute set in the " + + "AndroidManifest.xml. Ensure that all the dynamic features have the 'split' " + + "attribute correctly set in the AndroidManifest.xml."); + } + } + } + + if (!moduleNames.contains(BundleModuleName.create(BundleModuleName.BASE_MODULE_NAME))) { + throw new ValidationException( + "No base module found. At least one module must not have a 'split' attribute set in the " + + "AndroidManifest.xml."); + } + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/validation/ModuleTitleValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/ModuleTitleValidator.java index 9b0825b2..8a2bf651 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/ModuleTitleValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/ModuleTitleValidator.java @@ -16,15 +16,15 @@ package com.android.tools.build.bundletool.validation; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.entries; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.entries; import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.aapt.Resources.ResourceTable; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.BundleModule.ModuleDeliveryType; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +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.collect.ImmutableSet; import java.util.Optional; diff --git a/src/main/java/com/android/tools/build/bundletool/validation/NativeTargetingValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/NativeTargetingValidator.java index fb29d3ed..fab7840f 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/NativeTargetingValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/NativeTargetingValidator.java @@ -22,9 +22,9 @@ import com.android.bundle.Files.NativeLibraries; import com.android.bundle.Files.TargetedNativeDirectory; import com.android.bundle.Targeting.NativeDirectoryTargeting; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; 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 661b74ac..60510aad 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 @@ -20,14 +20,14 @@ import static com.google.common.collect.Sets.difference; import com.android.aapt.Resources.ResourceTable; -import com.android.tools.build.bundletool.exceptions.ResouceTableException.ReferencesFileOutsideOfResException; -import com.android.tools.build.bundletool.exceptions.ResouceTableException.ReferencesMissingFilesException; -import com.android.tools.build.bundletool.exceptions.ResouceTableException.ResourceTableMissingException; -import com.android.tools.build.bundletool.exceptions.ResouceTableException.UnreferencedResourcesException; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.ResourcesUtils; +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.utils.ResourcesUtils; import com.google.common.collect.ImmutableSet; /** diff --git a/src/main/java/com/android/tools/build/bundletool/validation/ValidatorRunner.java b/src/main/java/com/android/tools/build/bundletool/validation/ValidatorRunner.java index 4f645f36..76adf9e3 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/ValidatorRunner.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/ValidatorRunner.java @@ -67,7 +67,7 @@ public void validateBundleModules(ImmutableList modules) { private static void validateBundleUsingSubValidator(AppBundle bundle, SubValidator subValidator) { subValidator.validateBundle(bundle); validateBundleModulesUsingSubValidator( - ImmutableList.copyOf(bundle.getModules().values()), subValidator); + ImmutableList.copyOf(bundle.getFeatureModules().values()), subValidator); } private static void validateBundleModulesUsingSubValidator( diff --git a/src/main/java/com/android/tools/build/bundletool/xml/XPathResolver.java b/src/main/java/com/android/tools/build/bundletool/xml/XPathResolver.java index a31d63f2..cc0e1168 100755 --- a/src/main/java/com/android/tools/build/bundletool/xml/XPathResolver.java +++ b/src/main/java/com/android/tools/build/bundletool/xml/XPathResolver.java @@ -15,7 +15,7 @@ */ package com.android.tools.build.bundletool.xml; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableMap; import java.util.StringJoiner; import javax.xml.xpath.XPathConstants; diff --git a/src/main/java/com/android/tools/build/bundletool/xml/XmlNamespaceContext.java b/src/main/java/com/android/tools/build/bundletool/xml/XmlNamespaceContext.java index 90367ef8..88a92bb4 100755 --- a/src/main/java/com/android/tools/build/bundletool/xml/XmlNamespaceContext.java +++ b/src/main/java/com/android/tools/build/bundletool/xml/XmlNamespaceContext.java @@ -17,10 +17,10 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNamespace; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNamespace; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.google.common.collect.ImmutableMap; import java.util.Iterator; import java.util.stream.Stream; diff --git a/src/main/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverter.java b/src/main/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverter.java index f5e98eee..2a714655 100755 --- a/src/main/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverter.java +++ b/src/main/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverter.java @@ -18,16 +18,19 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.stream.Collectors.toList; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttribute; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNamespace; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNamespace; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Streams; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; @@ -47,6 +50,19 @@ public final class XmlProtoToXmlConverter { private static final String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/"; + private static final ImmutableMap COMMON_NAMESPACE_PREFIXES = + ImmutableMap.builder() + .put("http://schemas.android.com/apk/res/android", "android") + .put("http://schemas.android.com/apk/distribution", "dist") + .put("http://schemas.android.com/tools", "tools") + .build(); + + /** + * Index appended at the end of the namespace prefix created when the namespace declarations have + * been stripped out. + */ + private int nextPrefixIndex = 0; + /** * A map of the namespace URI to prefix. * @@ -153,15 +169,38 @@ private static String getAttributeTagName(XmlProtoAttribute protoAttribute) { } private String getPrefixForNamespace(String attrNamespaceUri) { - Deque prefixes = namespaceUriToPrefix.get(attrNamespaceUri); - if (prefixes == null || prefixes.isEmpty()) { - throw ValidationException.builder() - .withMessage("Prefix for URI '%s' not found", attrNamespaceUri) - .build(); - } + Deque prefixes = + namespaceUriToPrefix.computeIfAbsent( + attrNamespaceUri, + uri -> createDequeWithElement(getCommonPrefix(uri).orElseGet(() -> createNewPrefix()))); return prefixes.peekLast(); } + private Optional getCommonPrefix(String uri) { + String prefix = COMMON_NAMESPACE_PREFIXES.get(uri); + if (prefix == null || isNamespacePrefixInScope(prefix)) { + return Optional.empty(); + } + return Optional.of(prefix); + } + + /** Returns whether the given namespace {@code prefix} is used currently in the scope. */ + private boolean isNamespacePrefixInScope(String prefix) { + return namespaceUriToPrefix.values().stream() + .flatMap(queue -> Streams.stream(queue.iterator())) + .anyMatch(Predicate.isEqual(prefix)); + } + + private String createNewPrefix() { + return String.format("_unknown%d_", nextPrefixIndex++); + } + + private static Deque createDequeWithElement(String element) { + Deque queue = new ArrayDeque<>(); + queue.add(element); + return queue; + } + /** See {@link #convert(XmlProtoNode)}. */ private XmlProtoToXmlConverter() {} } diff --git a/src/main/proto/commands.proto b/src/main/proto/commands.proto index 830e1a3d..871ec66a 100755 --- a/src/main/proto/commands.proto +++ b/src/main/proto/commands.proto @@ -14,6 +14,9 @@ message BuildApksResult { // Metadata about BundleTool used to build the APKs. Bundletool bundletool = 2; + + // List of the created asset slices. + repeated AssetSlice asset_slice = 3; } // Variant is a group of APKs that covers a part of the device configuration @@ -34,6 +37,20 @@ message Variant { uint32 variant_number = 3; } +message AssetSlice { + // Asset slice targeting. + AssetSliceTargeting targeting = 1; + + AssetModuleMetadata asset_module_metadata = 2; + + // APKs. + repeated ApkDescription apk_description = 3; + + // Subversion for the Asset Module that this Asset Slice belongs to (i.e. the + // Asset Module it was generated from). + uint32 asset_module_subversion = 4; +} + // Represents a module. // For pre-L devices multiple modules (possibly all) may be merged into one. message ApkSet { @@ -63,6 +80,25 @@ message ModuleMetadata { ModuleTargeting targeting = 5; } +message AssetModuleMetadata { + // Module name. + string name = 1; + + // Indicates whether this module is marked "on demand" for persistent install. + bool on_demand = 2; + + // Metadata for instant installs. + InstantMetadata instant_metadata = 3; +} + +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; +} + message ApkDescription { ApkTargeting targeting = 1; diff --git a/src/main/proto/config.proto b/src/main/proto/config.proto index dd1c4738..eb4f5646 100755 --- a/src/main/proto/config.proto +++ b/src/main/proto/config.proto @@ -8,6 +8,8 @@ message BundleConfig { Bundletool bundletool = 1; Optimizations optimizations = 2; Compression compression = 3; + // Resources to be always kept in the master split. + MasterResources master_resources = 4; } message Bundletool { @@ -24,6 +26,12 @@ message Compression { repeated string uncompressed_glob = 1; } +// Resources to keep in the master split. +message MasterResources { + // Resource IDs to be kept in master splits. + repeated int32 resource_ids = 1; +} + message Optimizations { SplitsConfig splits_config = 1; // This is for uncompressing native libraries on M+ devices (L+ devices on diff --git a/src/main/proto/targeting.proto b/src/main/proto/targeting.proto index a1204ae5..81810412 100755 --- a/src/main/proto/targeting.proto +++ b/src/main/proto/targeting.proto @@ -30,6 +30,17 @@ message ApkTargeting { message ModuleTargeting { SdkVersionTargeting sdk_version_targeting = 1; repeated DeviceFeatureTargeting device_feature_targeting = 2; + UserCountriesTargeting user_countries_targeting = 3; +} + +// User Countries targeting describing an inclusive/exclusive list of country +// codes that module targets. +message UserCountriesTargeting { + // List of country codes in the two-letter CLDR territory format. + repeated string country_codes = 1; + + // Indicates if the list above is exclusive. + bool exclude = 2; } message ScreenDensity { @@ -123,6 +134,13 @@ message AssetsDirectoryTargeting { LanguageTargeting language = 4; } +// Targeting of individual asset slices. +message AssetSliceTargeting { + GraphicsApiTargeting graphics_api_targeting = 1; + LanguageTargeting language_targeting = 2; + TextureCompressionFormatTargeting texture_compression_format_targeting = 3; +} + // Targeting specific for directories under lib/. message NativeDirectoryTargeting { Abi abi = 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 a3006436..cf08a942 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 @@ -20,26 +20,30 @@ 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.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; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredBuilderPropertyException; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredFlagException; +import static com.google.common.base.StandardSystemProperty.USER_HOME; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.flags.FlagParser.FlagParseException; import com.android.tools.build.bundletool.model.Aapt2Command; import com.android.tools.build.bundletool.model.SigningConfiguration; +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.SystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.utils.files.FileUtils; import com.android.tools.build.bundletool.testing.Aapt2Helper; import com.android.tools.build.bundletool.testing.CertificateFactory; -import com.android.tools.build.bundletool.testing.FakeAndroidHomeVariableProvider; -import com.android.tools.build.bundletool.testing.FakeAndroidSerialVariableProvider; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.flags.FlagParser; -import com.android.tools.build.bundletool.utils.flags.FlagParser.FlagParseException; +import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; @@ -83,10 +87,9 @@ public class BuildApksCommandTest { private Path keystorePath; private final AdbServer fakeAdbServer = mock(AdbServer.class); - private final EnvironmentVariableProvider androidHomeProvider = - new FakeAndroidHomeVariableProvider("/android/home"); - private final EnvironmentVariableProvider androidSerialProvider = - new FakeAndroidSerialVariableProvider(DEVICE_ID); + private final SystemEnvironmentProvider systemEnvironmentProvider = + new FakeSystemEnvironmentProvider( + ImmutableMap.of(ANDROID_HOME, "/android/home", ANDROID_SERIAL, DEVICE_ID)); @BeforeClass public static void setUpClass() throws Exception { @@ -116,6 +119,7 @@ public void setUp() throws Exception { @Test public void buildingViaFlagsAndBuilderHasSameResult_defaults() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -123,9 +127,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_defaults() throws Exception "--bundle=" + bundlePath, "--output=" + outputFilePath, "--aapt2=" + AAPT2_PATH), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -133,15 +139,18 @@ public void buildingViaFlagsAndBuilderHasSameResult_defaults() throws Exception .setAapt2Command(commandViaFlags.getAapt2Command().get()) .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); + + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } // Remove this test when universal flag is deleted. @Test public void buildingViaFlagsWithUniversal_setsUniversalModeOnBuilder() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -150,9 +159,11 @@ public void buildingViaFlagsWithUniversal_setsUniversalModeOnBuilder() throws Ex "--output=" + outputFilePath, "--aapt2=" + AAPT2_PATH, "--universal"), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -161,14 +172,16 @@ public void buildingViaFlagsWithUniversal_setsUniversalModeOnBuilder() throws Ex .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .setApkBuildMode(UNIVERSAL) - .build(); + .setApkBuildMode(UNIVERSAL); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test public void buildingViaFlagsAndBuilderHasSameResult_optionalOptimizeFor() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -178,9 +191,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalOptimizeFor() throws "--aapt2=" + AAPT2_PATH, // Optional values. "--optimize-for=screen_density"), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -190,10 +205,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalOptimizeFor() throws .setAapt2Command(commandViaFlags.getAapt2Command().get()) .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test @@ -234,6 +250,7 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalSigning() throws Exc @Test public void buildingViaFlagsAndBuilderHasSameResult_optionalUniversal() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -243,9 +260,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalUniversal() throws E "--aapt2=" + AAPT2_PATH, // Optional values. "--mode=UNIVERSAL"), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -255,14 +274,16 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalUniversal() throws E .setAapt2Command(commandViaFlags.getAapt2Command().get()) .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test public void buildingViaFlagsAndBuilderHasSameResult_optionalOverwrite() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -272,8 +293,10 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalOverwrite() throws E "--aapt2=" + AAPT2_PATH, // Optional values. "--overwrite"), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -283,14 +306,16 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalOverwrite() throws E .setAapt2Command(commandViaFlags.getAapt2Command().get()) .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test public void buildingViaFlagsAndBuilderHasSameResult_deviceId() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -301,9 +326,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_deviceId() throws Exception "--connected-device", "--adb=" + ADB_PATH, "--aapt2=" + AAPT2_PATH), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -315,10 +342,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_deviceId() throws Exception .setAapt2Command(commandViaFlags.getAapt2Command().get()) .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test @@ -334,10 +362,10 @@ public void buildingViaFlagsAndBuilderHasSameResult_androidSerialVariable() thro "--adb=" + ADB_PATH, "--aapt2=" + AAPT2_PATH), new PrintStream(output), - androidSerialProvider, + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -349,10 +377,11 @@ public void buildingViaFlagsAndBuilderHasSameResult_androidSerialVariable() thro .setAapt2Command(commandViaFlags.getAapt2Command().get()) .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test @@ -500,19 +529,49 @@ public void printHelpDoesNotCrash() { } @Test - public void noKeystoreProvidedPrintsWarning() throws Exception { + public void noKeystoreProvidedPrintsWarning() { + SystemEnvironmentProvider provider = + new FakeSystemEnvironmentProvider( + /* variables= */ ImmutableMap.of( + ANDROID_HOME, "/android/home", ANDROID_SERIAL, DEVICE_ID), + /* properties= */ ImmutableMap.of(USER_HOME.key(), "/")); + ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildApksCommand.fromFlags( new FlagParser() .parse("--bundle=" + bundlePath, "--output=" + outputFilePath, "--aapt2=" + AAPT2_PATH), new PrintStream(output), - androidHomeProvider, + provider, fakeAdbServer); assertThat(new String(output.toByteArray(), UTF_8)) .contains("WARNING: The APKs won't be signed"); } + @Test + public void noKeystoreProvidedPrintsWarning_debugKeystore() throws Exception { + Path debugKeystorePath = tmpDir.resolve(".android").resolve("debug.keystore"); + FileUtils.createParentDirectories(debugKeystorePath); + createDebugKeystore(debugKeystorePath); + + SystemEnvironmentProvider provider = + new FakeSystemEnvironmentProvider( + /* variables= */ ImmutableMap.of( + ANDROID_HOME, "/android/home", ANDROID_SERIAL, DEVICE_ID), + /* properties= */ ImmutableMap.of(USER_HOME.key(), tmpDir.toString())); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + BuildApksCommand.fromFlags( + new FlagParser() + .parse("--bundle=" + bundlePath, "--output=" + outputFilePath, "--aapt2=" + AAPT2_PATH), + new PrintStream(output), + provider, + fakeAdbServer); + + assertThat(new String(output.toByteArray(), UTF_8)) + .contains("INFO: The APKs will be signed with the debug keystore"); + } + @Test public void keystoreProvidedDoesNotPrintWarning() throws Exception { ByteArrayOutputStream output = new ByteArrayOutputStream(); @@ -527,10 +586,22 @@ public void keystoreProvidedDoesNotPrintWarning() throws Exception { "--ks-pass=pass:" + KEYSTORE_PASSWORD, "--key-pass=pass:" + KEY_PASSWORD), new PrintStream(output), - androidHomeProvider, + systemEnvironmentProvider, fakeAdbServer); assertThat(new String(output.toByteArray(), UTF_8)) .doesNotContain("WARNING: The APKs won't be signed"); } + + private static void createDebugKeystore(Path path) throws Exception { + KeyPair keyPair = KeyPairGenerator.getInstance("RSA").genKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + Certificate certificate = + CertificateFactory.buildSelfSignedCertificate(keyPair, "CN=Android Debug,O=Android,C=US"); + KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(/* stream= */ null, "android".toCharArray()); + keystore.setKeyEntry( + "AndroidDebugKey", privateKey, "android".toCharArray(), new Certificate[] {certificate}); + keystore.store(new FileOutputStream(path.toFile()), "android".toCharArray()); + } } diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksConnectedDeviceTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksConnectedDeviceTest.java index c76be49c..c3c00684 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksConnectedDeviceTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksConnectedDeviceTest.java @@ -28,6 +28,7 @@ import static com.android.tools.build.bundletool.testing.DeviceFactory.locales; import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; +import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_HOME; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.truth.zip.TruthZip.assertThat; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -41,17 +42,18 @@ import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.ddmlib.IDevice.DeviceState; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; import com.android.tools.build.bundletool.io.AppBundleSerializer; import com.android.tools.build.bundletool.model.AppBundle; +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.SystemEnvironmentProvider; import com.android.tools.build.bundletool.testing.AppBundleBuilder; import com.android.tools.build.bundletool.testing.FakeAdbServer; -import com.android.tools.build.bundletool.testing.FakeAndroidHomeVariableProvider; import com.android.tools.build.bundletool.testing.FakeDevice; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.flags.FlagParser; +import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.nio.file.Files; import java.nio.file.Path; import java.util.zip.ZipFile; @@ -78,7 +80,7 @@ public class BuildApksConnectedDeviceTest { private AdbServer fakeAdbServer = new FakeAdbServer(/* hasInitialDeviceList= */ true, /* devices= */ ImmutableList.of()); - private EnvironmentVariableProvider androidHomeProvider; + private SystemEnvironmentProvider androidHomeProvider; @Before public void setUp() throws Exception { @@ -89,7 +91,8 @@ public void setUp() throws Exception { sdkDirPath = Files.createDirectory(tmpDir.resolve("android-sdk")); setUpAdb(sdkDirPath); - this.androidHomeProvider = new FakeAndroidHomeVariableProvider(sdkDirPath.toString()); + this.androidHomeProvider = + new FakeSystemEnvironmentProvider(ImmutableMap.of(ANDROID_HOME, sdkDirPath.toString())); } private Path setUpAdb(Path sdkDirPath) throws Exception { @@ -114,7 +117,7 @@ public void connectedDevice_flagsEquivalent_androidHome() { androidHomeProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -125,10 +128,11 @@ public void connectedDevice_flagsEquivalent_androidHome() { // Must copy instance of the internal executor service. .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(androidHomeProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test @@ -148,7 +152,7 @@ public void connectedDevice_flagsEquivalent_explicitAdbPath() throws Exception { androidHomeProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -159,10 +163,11 @@ public void connectedDevice_flagsEquivalent_explicitAdbPath() throws Exception { // Must copy instance of the internal executor service. .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(androidHomeProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test @@ -180,7 +185,7 @@ public void connectedDevice_flagsEquivalent_deviceId() { androidHomeProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) @@ -192,10 +197,11 @@ public void connectedDevice_flagsEquivalent_deviceId() { // Must copy instance of the internal executor service. .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(androidHomeProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksDeviceSpecTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksDeviceSpecTest.java index 81890299..dfa1ea45 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksDeviceSpecTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksDeviceSpecTest.java @@ -15,7 +15,11 @@ */ package com.android.tools.build.bundletool.commands; +import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.HDPI; +import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.XHDPI; import static com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode.UNIVERSAL; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.instantApkVariants; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.splitApkVariants; import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractTocFromApkSetFile; import static com.android.tools.build.bundletool.testing.AppBundleFactory.createInstantBundle; import static com.android.tools.build.bundletool.testing.AppBundleFactory.createLdpiHdpiAppBundle; @@ -33,8 +37,6 @@ import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.truth.zip.TruthZip.assertThat; -import static com.android.tools.build.bundletool.utils.ResultUtils.instantApkVariants; -import static com.android.tools.build.bundletool.utils.ResultUtils.splitApkVariants; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -44,16 +46,20 @@ import com.android.bundle.Commands.BuildApksResult; import com.android.bundle.Commands.Variant; import com.android.bundle.Devices.DeviceSpec; -import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; import com.android.tools.build.bundletool.io.AppBundleSerializer; import com.android.tools.build.bundletool.model.AppBundle; +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.SystemEnvironmentProvider; import com.android.tools.build.bundletool.testing.AppBundleBuilder; import com.android.tools.build.bundletool.testing.FakeAdbServer; -import com.android.tools.build.bundletool.utils.flags.FlagParser; +import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.nio.file.Path; import java.util.zip.ZipFile; import org.junit.Before; @@ -77,6 +83,8 @@ public class BuildApksDeviceSpecTest { private Path outputFilePath; private final AdbServer fakeAdbServer = new FakeAdbServer(/* hasInitialDeviceList= */ true, /* devices= */ ImmutableList.of()); + private final SystemEnvironmentProvider systemEnvironmentProvider = + new FakeSystemEnvironmentProvider(/* variables= */ ImmutableMap.of()); @Before public void setUp() throws Exception { @@ -88,8 +96,9 @@ public void setUp() throws Exception { @Test public void deviceSpec_flagsEquivalent() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile(DeviceSpec.getDefaultInstance(), tmpDir.resolve("device.json")); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DeviceSpec deviceSpec = deviceWithSdk(28); + Path deviceSpecPath = createDeviceSpecFile(deviceSpec, tmpDir.resolve("device.json")); BuildApksCommand commandViaFlags = BuildApksCommand.fromFlags( new FlagParser() @@ -97,26 +106,30 @@ public void deviceSpec_flagsEquivalent() throws Exception { "--bundle=" + bundlePath, "--output=" + outputFilePath, "--device-spec=" + deviceSpecPath), + new PrintStream(output), + systemEnvironmentProvider, fakeAdbServer); - BuildApksCommand commandViaBuilder = + BuildApksCommand.Builder commandViaBuilder = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) // Optional values. - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) // Must copy instance of the internal executor service. .setExecutorServiceInternal(commandViaFlags.getExecutorService()) .setExecutorServiceCreatedByBundleTool(true) - .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()) - .build(); + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); - assertThat(commandViaBuilder).isEqualTo(commandViaFlags); + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } @Test public void deviceSpec_universalApk_throws() throws Exception { - Path deviceSpecPath = createDeviceSpecFile(deviceWithSdk(21), tmpDir.resolve("device.json")); + DeviceSpec deviceSpec = deviceWithSdk(21); AppBundle appBundle = new AppBundleBuilder() @@ -128,19 +141,19 @@ public void deviceSpec_universalApk_throws() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .setApkBuildMode(UNIVERSAL); Throwable exception = assertThrows(ValidationException.class, () -> command.build()); assertThat(exception) .hasMessageThat() .contains( - "Optimizing for device spec only possible when running with 'default' mode flag."); + "Optimizing for device spec not possible when running with 'universal' mode flag."); } @Test public void deviceSpec_andConnectedDevice_throws() throws Exception { - Path deviceSpecPath = createDeviceSpecFile(deviceWithSdk(21), tmpDir.resolve("device.json")); + DeviceSpec deviceSpec = deviceWithSdk(21); AppBundle appBundle = new AppBundleBuilder() @@ -152,7 +165,7 @@ public void deviceSpec_andConnectedDevice_throws() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .setGenerateOnlyForConnectedDevice(true); Throwable exception = assertThrows(ValidationException.class, () -> command.build()); @@ -163,8 +176,7 @@ public void deviceSpec_andConnectedDevice_throws() throws Exception { @Test public void deviceSpec_correctSplitsGenerated() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile(lDeviceWithDensity(DensityAlias.XHDPI), tmpDir.resolve("device.json")); + DeviceSpec deviceSpec = lDeviceWithDensity(XHDPI); bundleSerializer.writeToDisk(createLdpiHdpiAppBundle(), bundlePath); @@ -172,7 +184,7 @@ public void deviceSpec_correctSplitsGenerated() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Path apksArchive = command.execute(); @@ -192,12 +204,8 @@ public void deviceSpec_correctSplitsGenerated() throws Exception { @Test public void deviceSpec_correctStandaloneGenerated() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile( - mergeSpecs( - sdkVersion(19), abis("x86"), - locales("en-US"), density(DensityAlias.HDPI)), - tmpDir.resolve("device.json")); + DeviceSpec deviceSpec = + mergeSpecs(sdkVersion(19), abis("x86"), locales("en-US"), density(HDPI)); bundleSerializer.writeToDisk(createLdpiHdpiAppBundle(), bundlePath); @@ -205,7 +213,7 @@ public void deviceSpec_correctStandaloneGenerated() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Path apksArchive = command.execute(); @@ -225,15 +233,13 @@ public void deviceSpec_correctStandaloneGenerated() throws Exception { public void deviceSpecL_bundleTargetsPreL_throws() throws Exception { bundleSerializer.writeToDisk(createMaxSdkBundle(/* KitKat */ 19), bundlePath); - Path deviceSpecPath = - createDeviceSpecFile( - lDeviceWithDensity(DensityAlias.XHDPI), outputDir.resolve("device.json")); + DeviceSpec deviceSpec = lDeviceWithDensity(XHDPI); BuildApksCommand command = BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Throwable exception = assertThrows(CommandExecutionException.class, () -> command.execute()); @@ -246,12 +252,10 @@ public void deviceSpecL_bundleTargetsPreL_throws() throws Exception { @Test public void deviceSpecPreL_bundleTargetsLPlus_throws() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile( - mergeSpecs( - sdkVersion(/* KitKat */ 19), abis("x86"), - locales("en-US"), density(DensityAlias.XHDPI)), - outputDir.resolve("device.json")); + DeviceSpec deviceSpec = + mergeSpecs( + sdkVersion(/* KitKat */ 19), abis("x86"), + locales("en-US"), density(XHDPI)); bundleSerializer.writeToDisk(createMinSdkBundle(21), bundlePath); @@ -259,7 +263,7 @@ public void deviceSpecPreL_bundleTargetsLPlus_throws() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Throwable exception = assertThrows(CommandExecutionException.class, () -> command.execute()); @@ -271,12 +275,10 @@ public void deviceSpecPreL_bundleTargetsLPlus_throws() throws Exception { @Ignore("Re-enable when minSdk version propagation is fixed.") @Test public void deviceSpecL_bundleTargetsMPlus_throws() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile( - mergeSpecs( - sdkVersion(/* Lollipop */ 21), abis("x86"), - locales("en-US"), density(DensityAlias.XHDPI)), - outputDir.resolve("device.json")); + DeviceSpec deviceSpec = + mergeSpecs( + sdkVersion(/* Lollipop */ 21), abis("x86"), + locales("en-US"), density(XHDPI)); bundleSerializer.writeToDisk(createMinSdkBundle(/* Marshmallow */ 23), bundlePath); @@ -284,7 +286,7 @@ public void deviceSpecL_bundleTargetsMPlus_throws() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Throwable exception = assertThrows(CommandExecutionException.class, () -> command.execute()); @@ -293,12 +295,10 @@ public void deviceSpecL_bundleTargetsMPlus_throws() throws Exception { @Test public void deviceSpecMips_bundleTargetsX86_throws() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile( - mergeSpecs( - sdkVersion(/* Lollipop */ 21), abis("mips"), - locales("en-US"), density(DensityAlias.XHDPI)), - outputDir.resolve("device.json")); + DeviceSpec deviceSpec = + mergeSpecs( + sdkVersion(/* Lollipop */ 21), abis("mips"), + locales("en-US"), density(XHDPI)); bundleSerializer.writeToDisk(createX86AppBundle(), bundlePath); @@ -306,7 +306,7 @@ public void deviceSpecMips_bundleTargetsX86_throws() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Throwable exception = assertThrows(CommandExecutionException.class, () -> command.execute()); @@ -320,12 +320,10 @@ public void deviceSpecMips_bundleTargetsX86_throws() throws Exception { @Ignore("Re-enable when maxSdkVersion is validated in App Bundle and used in device matching.") @Test public void deviceSpecN_bundleTargetsLtoM() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile( - mergeSpecs( - sdkVersion(/* Nougat */ 25), abis("x86"), - locales("en-US"), density(DensityAlias.XHDPI)), - outputDir.resolve("device.json")); + DeviceSpec deviceSpec = + mergeSpecs( + sdkVersion(/* Nougat */ 25), abis("x86"), + locales("en-US"), density(XHDPI)); bundleSerializer.writeToDisk( createMinMaxSdkAppBundle(/* Lollipop */ 21, /* Marshmallow */ 23), bundlePath); @@ -334,7 +332,7 @@ public void deviceSpecN_bundleTargetsLtoM() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Throwable exception = assertThrows(CommandExecutionException.class, () -> command.execute()); @@ -343,8 +341,7 @@ public void deviceSpecN_bundleTargetsLtoM() throws Exception { @Test public void deviceSpec_instantSplitsGenerated() throws Exception { - Path deviceSpecPath = - createDeviceSpecFile(lDeviceWithDensity(DensityAlias.XHDPI), tmpDir.resolve("device.json")); + DeviceSpec deviceSpec = lDeviceWithDensity(XHDPI); bundleSerializer.writeToDisk(createInstantBundle(), bundlePath); @@ -352,7 +349,7 @@ public void deviceSpec_instantSplitsGenerated() throws Exception { BuildApksCommand.builder() .setBundlePath(bundlePath) .setOutputFile(outputFilePath) - .setDeviceSpecPath(deviceSpecPath) + .setDeviceSpec(deviceSpec) .build(); Path apksArchive = command.execute(); @@ -378,9 +375,7 @@ public void deviceSpec_instantSplitsGenerated() throws Exception { } private static ImmutableList apkNamesInSet(ApkSet apkSet) { - return apkSet - .getApkDescriptionList() - .stream() + return apkSet.getApkDescriptionList().stream() .map(ApkDescription::getPath) .collect(toImmutableList()); } 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 51f1846c..20be067e 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 @@ -27,10 +27,21 @@ 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.LANGUAGE; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.instantApkVariants; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.splitApkVariants; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.standaloneApkVariants; +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.testing.Aapt2Helper.AAPT2_PATH; import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractFromApkSetFile; import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractTocFromApkSetFile; import static com.android.tools.build.bundletool.testing.ApkSetUtils.parseTocFromFile; +import static com.android.tools.build.bundletool.testing.DeviceFactory.abis; +import static com.android.tools.build.bundletool.testing.DeviceFactory.density; +import static com.android.tools.build.bundletool.testing.DeviceFactory.locales; +import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; +import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; import static com.android.tools.build.bundletool.testing.FileUtils.uncompressGzipFile; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifestForFeature; @@ -46,11 +57,7 @@ import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.MDPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.TEST_LABEL_RESOURCE_ID; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.USER_PACKAGE_OFFSET; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.createResourceTable; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.fileReference; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.locale; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.packageWithTestLabel; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.resourceTable; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.resourceTableWithTestLabel; import static com.android.tools.build.bundletool.testing.TargetingUtils.abiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.alternativeLanguageTargeting; @@ -75,12 +82,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.filesUnderPath; import static com.android.tools.build.bundletool.testing.truth.zip.TruthZip.assertThat; -import static com.android.tools.build.bundletool.utils.ResultUtils.instantApkVariants; -import static com.android.tools.build.bundletool.utils.ResultUtils.splitApkVariants; -import static com.android.tools.build.bundletool.utils.ResultUtils.standaloneApkVariants; -import static com.android.tools.build.bundletool.utils.ResultUtils.systemApkVariants; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset; @@ -109,22 +110,26 @@ import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.AbiTargeting; import com.android.bundle.Targeting.ApkTargeting; +import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.bundle.Targeting.SdkVersion; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.TestData; import com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.io.AppBundleSerializer; import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.model.Aapt2Command; import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.ApkModifier; import com.android.tools.build.bundletool.model.AppBundle; -import com.android.tools.build.bundletool.model.ResourceId; import com.android.tools.build.bundletool.model.SigningConfiguration; 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.utils.files.FilePreconditions; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.android.tools.build.bundletool.testing.Aapt2Helper; import com.android.tools.build.bundletool.testing.ApkSetUtils; import com.android.tools.build.bundletool.testing.AppBundleBuilder; @@ -132,11 +137,8 @@ import com.android.tools.build.bundletool.testing.CertificateFactory; import com.android.tools.build.bundletool.testing.FakeAdbServer; import com.android.tools.build.bundletool.testing.FileUtils; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import com.android.tools.build.bundletool.testing.truth.zip.TruthZip; -import com.android.tools.build.bundletool.utils.files.FilePreconditions; -import com.android.tools.build.bundletool.utils.flags.FlagParser; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; -import com.android.tools.build.bundletool.version.BundleToolVersion; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultiset; @@ -719,10 +721,17 @@ public void buildApksCommand_universal_generatesSingleApkWithNoOptimizations() t .addFile("res/drawable-ldpi/image.jpg") .addFile("res/drawable-mdpi/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + LDPI, + "res/drawable-ldpi/image.jpg", + MDPI, + "res/drawable-mdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app"))) .build(); bundleSerializer.writeToDisk(appBundle, bundlePath); @@ -786,10 +795,17 @@ public void buildApksCommand_compressedSystem_generatesSingleApkWithEmptyOptimiz .addFile("res/drawable-ldpi/image.jpg") .addFile("res/drawable-mdpi/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + LDPI, + "res/drawable-ldpi/image.jpg", + MDPI, + "res/drawable-mdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app"))) .setBundleConfig( BundleConfigBuilder.create() @@ -880,10 +896,17 @@ public void buildApksCommand_system_generatesSingleApkWithEmptyOptimizations() t .addFile("res/drawable-ldpi/image.jpg") .addFile("res/drawable-mdpi/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + LDPI, + "res/drawable-ldpi/image.jpg", + MDPI, + "res/drawable-mdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app"))) .setBundleConfig( BundleConfigBuilder.create() @@ -1271,6 +1294,75 @@ public void buildApksCommand_system_oneModuleManyVariants( } } + @Test + @Theory + public void buildApksCommand_systemWithDeviceSpec_oneModuleSingleVariant( + @FromDataPoints("systemApkBuildModes") ApkBuildMode systemApkBuildMode) throws Exception { + bundlePath = FileUtils.getRandomFilePath(tmp, "bundle-", ".aab"); + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + "base", + builder -> + builder + .addFile("dex/classes.dex") + .addFile("lib/x86/libsome.so") + .addFile("lib/x86_64/libsome.so") + .addFile("lib/mips/libsome.so") + .setNativeConfig( + nativeLibraries( + targetedNativeDirectory( + "lib/x86", nativeDirectoryTargeting(AbiAlias.X86)), + targetedNativeDirectory( + "lib/x86_64", nativeDirectoryTargeting(AbiAlias.X86_64)), + targetedNativeDirectory( + "lib/mips", nativeDirectoryTargeting(AbiAlias.MIPS)))) + .setManifest(androidManifest("com.test.app"))) + .setBundleConfig( + BundleConfigBuilder.create() + .addSplitDimension(Value.ABI) + .addSplitDimension(Value.SCREEN_DENSITY, /* negate= */ true) + .build()) + .build(); + bundleSerializer.writeToDisk(appBundle, bundlePath); + + BuildApksCommand command = + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setApkBuildMode(systemApkBuildMode) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .setDeviceSpec( + mergeSpecs( + sdkVersion(28), abis("x86"), density(DensityAlias.MDPI), locales("en-US"))) + .build(); + + Path apkSetFilePath = execute(command); + ZipFile apkSetFile = new ZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + + assertThat(result.getVariantList()).hasSize(1); + Variant x86Variant = result.getVariant(0); + assertThat(x86Variant.getTargeting()) + .ignoringRepeatedFieldOrder() + .isEqualTo( + mergeVariantTargeting( + variantAbiTargeting(AbiAlias.X86, ImmutableSet.of(AbiAlias.X86_64, AbiAlias.MIPS)), + variantSdkTargeting(LOWEST_SDK_VERSION))); + assertThat(x86Variant.getApkSetList()).hasSize(1); + ApkSet apkSet = x86Variant.getApkSet(0); + if (systemApkBuildMode.equals(SYSTEM)) { + // Single System APK. + assertThat(apkSet.getApkDescriptionList()).hasSize(1); + } else { + // Stub and Compressed APK. + assertThat(apkSet.getApkDescriptionList()).hasSize(2); + } + apkSet + .getApkDescriptionList() + .forEach(apkDescription -> assertThat(apkSetFile).hasFile(apkDescription.getPath())); + } + @Test public void buildApksCommand_standalone_mixedTargeting() throws Exception { bundlePath = FileUtils.getRandomFilePath(tmp, "bundle-", ".aab"); @@ -1854,9 +1946,7 @@ private void runSingleConcurrencyTest_disableNativeLibrariesOptimization(int thr .addFile("assets/file.txt") .addFile("dex/classes.dex") .setManifest(androidManifest("com.test.app")) - .setResourceTable( - resourceTable( - packageWithTestLabel("Test feature", USER_PACKAGE_OFFSET - 1)))) + .setResourceTable(resourceTableWithTestLabel("Test feature"))) .addModule( "abi_feature", builder -> @@ -1872,9 +1962,7 @@ private void runSingleConcurrencyTest_disableNativeLibrariesOptimization(int thr .setManifest( androidManifestForFeature( "com.test.app", - withTitle( - "@string/test_label", - makeResourceIdentifier(USER_PACKAGE_OFFSET - 1, 0x01, 0x01))))) + withTitle("@string/test_label", TEST_LABEL_RESOURCE_ID)))) .addModule( "language_feature", builder -> @@ -1884,19 +1972,25 @@ private void runSingleConcurrencyTest_disableNativeLibrariesOptimization(int thr .addFile("res/drawable-fr/image.jpg") .addFile("res/drawable-pl/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference( - "res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-cz/image.jpg", locale("cz")), - fileReference("res/drawable-fr/image.jpg", locale("fr")), - fileReference("res/drawable-pl/image.jpg", locale("pl")))) + new ResourceTableBuilder() + .addPackage("com.test.app", USER_PACKAGE_OFFSET - 1) + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image.jpg", + locale("cz"), + "res/drawable-cz/image.jpg", + locale("fr"), + "res/drawable-fr/image.jpg", + locale("pl"), + "res/drawable-pl/image.jpg")) + .build()) .setManifest( androidManifestForFeature( "com.test.app", - withTitle( - "@string/test_label", - makeResourceIdentifier(USER_PACKAGE_OFFSET - 1, 0x01, 0x01))))) + withTitle("@string/test_label", TEST_LABEL_RESOURCE_ID)))) .setBundleConfig( BundleConfigBuilder.create().setUncompressNativeLibraries(false).build()) .build(); @@ -2144,9 +2238,7 @@ public void extractApkSet_outputApksWithoutArchive() throws Exception { .addFile("assets/file.txt") .addFile("dex/classes.dex") .setManifest(androidManifest("com.test.app")) - .setResourceTable( - resourceTable( - packageWithTestLabel("test_label", USER_PACKAGE_OFFSET - 1)))) + .setResourceTable(resourceTableWithTestLabel("test_label"))) .addModule( "abi_feature", builder -> @@ -2162,9 +2254,7 @@ public void extractApkSet_outputApksWithoutArchive() throws Exception { .setManifest( androidManifestForFeature( "com.test.app", - withTitle( - "@string/test_label", - makeResourceIdentifier(USER_PACKAGE_OFFSET - 1, 0x01, 0x01))))) + withTitle("@string/test_label", TEST_LABEL_RESOURCE_ID)))) .addModule( "language_feature", builder -> @@ -2174,19 +2264,25 @@ public void extractApkSet_outputApksWithoutArchive() throws Exception { .addFile("res/drawable-fr/image.jpg") .addFile("res/drawable-pl/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference( - "res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-cz/image.jpg", locale("cz")), - fileReference("res/drawable-fr/image.jpg", locale("fr")), - fileReference("res/drawable-pl/image.jpg", locale("pl")))) + new ResourceTableBuilder() + .addPackage("com.test.app", USER_PACKAGE_OFFSET - 1) + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image.jpg", + locale("cz"), + "res/drawable-cz/image.jpg", + locale("fr"), + "res/drawable-fr/image.jpg", + locale("pl"), + "res/drawable-pl/image.jpg")) + .build()) .setManifest( androidManifestForFeature( "com.test.app", - withTitle( - "@string/test_label", - makeResourceIdentifier(USER_PACKAGE_OFFSET - 1, 0x01, 0x01))))) + withTitle("@string/test_label", TEST_LABEL_RESOURCE_ID)))) .build(); bundleSerializer.writeToDisk(appBundle, bundlePath); @@ -2747,15 +2843,6 @@ private Path execute(BuildApksCommand command) { return new BuildApksManager(command).execute(tmpDir); } - private static int makeResourceIdentifier(int pkgId, int typeId, int entryId) { - return ResourceId.builder() - .setPackageId(pkgId) - .setTypeId(typeId) - .setEntryId(entryId) - .build() - .getFullResourceId(); - } - private ImmutableMap extractStandaloneVariantsByTargeting( BuildApksResult result) { return Maps.uniqueIndex( diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksResourcePinningTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksResourcePinningTest.java new file mode 100755 index 00000000..dc1fec46 --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksResourcePinningTest.java @@ -0,0 +1,261 @@ +/* + * 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.commands; + +import static com.android.tools.build.bundletool.model.utils.ResultUtils.splitApkVariants; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.standaloneApkVariants; +import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractFromApkSetFile; +import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractTocFromApkSetFile; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFusingAttribute; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMinSdkVersion; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withOnDemandDelivery; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTitle; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.locale; +import static com.android.tools.build.bundletool.testing.TestUtils.filesUnderPath; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; + +import com.android.aapt.ConfigurationOuterClass.Configuration; +import com.android.aapt.Resources.ResourceTable; +import com.android.bundle.Commands.ApkDescription; +import com.android.bundle.Commands.ApkSet; +import com.android.bundle.Commands.BuildApksResult; +import com.android.tools.build.bundletool.io.AppBundleSerializer; +import com.android.tools.build.bundletool.model.Aapt2Command; +import com.android.tools.build.bundletool.model.AppBundle; +import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.testing.Aapt2Helper; +import com.android.tools.build.bundletool.testing.AppBundleBuilder; +import com.android.tools.build.bundletool.testing.BundleConfigBuilder; +import com.android.tools.build.bundletool.testing.FileUtils; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** End to end tests for resources pinning to master splits. */ +@RunWith(JUnit4.class) +public class BuildApksResourcePinningTest { + + private final AppBundleSerializer bundleSerializer = new AppBundleSerializer(); + + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path tmpDir; + + private Path bundlePath; + private Path outputDir; + private Path outputFilePath; + private final Aapt2Command aapt2Command = Aapt2Helper.getAapt2Command(); + + @Before + public void setUp() throws Exception { + tmpDir = tmp.getRoot().toPath(); + bundlePath = tmpDir.resolve("bundle"); + outputDir = tmp.newFolder("output").toPath(); + outputFilePath = outputDir.resolve("app.apks"); + } + + @Test + public void resourceIds_pinnedToMasterSplits() throws Exception { + bundlePath = FileUtils.getRandomFilePath(tmp, "bundle-", ".aab"); + + ResourceTable baseResourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app") + // 0x7f010000 + .addStringResource("test_label", "Module title") + // 0x7f020000 + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image1.jpg", + locale("fr"), + "res/drawable-fr/image1.jpg")) + // 0x7f020001 + .addFileResourceForMultipleConfigs( + "drawable", + "image2", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image2.jpg", + locale("fr"), + "res/drawable-fr/image2.jpg")) + .build(); + + ResourceTable featureResourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app.feature", 0x80) + // 0x80010000 + .addFileResourceForMultipleConfigs( + "drawable", + "image3", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image3.jpg", + locale("fr"), + "res/drawable-fr/image3.jpg")) + // 0x80010001 + .addFileResourceForMultipleConfigs( + "drawable", + "image4", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image4.jpg", + locale("fr"), + "res/drawable-fr/image4.jpg")) + .build(); + + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + "base", + builder -> + builder + .addFile("dex/classes.dex") + .addFile("res/drawable/image1.jpg") + .addFile("res/drawable-fr/image1.jpg") + .addFile("res/drawable/image2.jpg") + .addFile("res/drawable-fr/image2.jpg") + .setManifest(androidManifest("com.test.app", withMinSdkVersion(14))) + .setResourceTable(baseResourceTable)) + .addModule( + "feature", + builder -> + builder + .addFile("res/drawable/image3.jpg") + .addFile("res/drawable-fr/image3.jpg") + .addFile("res/drawable/image4.jpg") + .addFile("res/drawable-fr/image4.jpg") + .setManifest( + androidManifest( + "com.test.app", + withMinSdkVersion(14), + withOnDemandDelivery(), + withFusingAttribute(true), + withTitle("@string/test_label", 0x7f010000))) + .setResourceTable(featureResourceTable)) + .setBundleConfig( + BundleConfigBuilder.create() + .addResourcePinnedToMasterSplit(0x7f020000) // image1 from "base" module + .addResourcePinnedToMasterSplit(0x80010001) // image4 from "feature" module + .build()) + .build(); + + bundleSerializer.writeToDisk(appBundle, bundlePath); + + BuildApksCommand command = + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .build(); + + Path apkSetFilePath = new BuildApksManager(command).execute(tmpDir); + ZipFile apkSetFile = new ZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + + // Verifying that standalone APKs contain all entries. + assertThat(standaloneApkVariants(result)).hasSize(1); + List standaloneApkSets = standaloneApkVariants(result).get(0).getApkSetList(); + assertThat(standaloneApkSets).hasSize(1); + List standaloneApkDescription = + standaloneApkSets.get(0).getApkDescriptionList(); + assertThat(standaloneApkDescription).hasSize(1); + File standaloneApkFile = + extractFromApkSetFile(apkSetFile, standaloneApkDescription.get(0).getPath(), outputDir); + try (ZipFile standaloneZip = new ZipFile(standaloneApkFile)) { + assertThat(filesUnderPath(standaloneZip, ZipPath.create("res"))) + .containsExactly( + "res/drawable/image1.jpg", + "res/drawable-fr/image1.jpg", + "res/drawable/image2.jpg", + "res/drawable-fr/image2.jpg", + "res/drawable/image3.jpg", + "res/drawable-fr/image3.jpg", + "res/drawable/image4.jpg", + "res/drawable-fr/image4.jpg", + "res/xml/splits0.xml"); + } + + // Verifying split APKs. + assertThat(splitApkVariants(result)).hasSize(1); + List splitApkSetList = splitApkVariants(result).get(0).getApkSetList(); + Map modules = + Maps.uniqueIndex(splitApkSetList, apkSet -> apkSet.getModuleMetadata().getName()); + assertThat(modules.keySet()).containsExactly("base", "feature"); + + List baseModuleApks = modules.get("base").getApkDescriptionList(); + assertThat(baseModuleApks).hasSize(2); + Map apkBaseMaster = + Maps.uniqueIndex( + baseModuleApks, + apkDescription -> apkDescription.getSplitApkMetadata().getIsMasterSplit()); + + ApkDescription baseMaster = apkBaseMaster.get(/* isMasterSplit= */ true); + File baseMasterFile = extractFromApkSetFile(apkSetFile, baseMaster.getPath(), outputDir); + try (ZipFile baseMasterZip = new ZipFile(baseMasterFile)) { + assertThat(filesUnderPath(baseMasterZip, ZipPath.create("res"))) + .containsExactly( + "res/drawable/image1.jpg", + "res/drawable-fr/image1.jpg", + "res/drawable/image2.jpg", + "res/xml/splits0.xml"); + } + + ApkDescription baseFr = apkBaseMaster.get(/* isMasterSplit= */ false); + File baseFrFile = extractFromApkSetFile(apkSetFile, baseFr.getPath(), outputDir); + try (ZipFile baseFrZip = new ZipFile(baseFrFile)) { + assertThat(filesUnderPath(baseFrZip, ZipPath.create("res"))) + .containsExactly("res/drawable-fr/image2.jpg"); + } + + List featureModuleApks = modules.get("feature").getApkDescriptionList(); + assertThat(featureModuleApks).hasSize(2); + Map apkFeatureMaster = + Maps.uniqueIndex( + featureModuleApks, + apkDescription -> apkDescription.getSplitApkMetadata().getIsMasterSplit()); + + ApkDescription featureMaster = apkFeatureMaster.get(/* isMasterSplit= */ true); + File featureMasterFile = extractFromApkSetFile(apkSetFile, featureMaster.getPath(), outputDir); + try (ZipFile featureMasterZip = new ZipFile(featureMasterFile)) { + assertThat(filesUnderPath(featureMasterZip, ZipPath.create("res"))) + .containsExactly( + "res/drawable/image3.jpg", "res/drawable/image4.jpg", "res/drawable-fr/image4.jpg"); + } + + ApkDescription featureFr = apkFeatureMaster.get(/* isMasterSplit= */ false); + File featureFrFile = extractFromApkSetFile(apkSetFile, featureFr.getPath(), outputDir); + try (ZipFile featureFrZip = new ZipFile(featureFrFile)) { + assertThat(filesUnderPath(featureFrZip, ZipPath.create("res"))) + .containsExactly("res/drawable-fr/image3.jpg"); + } + } +} diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildBundleCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildBundleCommandTest.java index 5a9f85aa..26143c2d 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildBundleCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildBundleCommandTest.java @@ -16,19 +16,23 @@ package com.android.tools.build.bundletool.commands; +import static com.android.bundle.Targeting.Abi.AbiAlias.ARM64_V8A; +import static com.android.bundle.Targeting.Abi.AbiAlias.ARMEABI_V7A; import static com.android.bundle.Targeting.Abi.AbiAlias.X86; +import static com.android.bundle.Targeting.Abi.AbiAlias.X86_64; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFusingAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withHasCode; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withOnDemandAttribute; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withSplitId; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withUsesSplit; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.createResourceTable; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.fileReference; -import static com.android.tools.build.bundletool.testing.TargetingUtils.apexImageTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.apexImages; import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.graphicsApiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeAssetsTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.multiAbiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.openGlVersionFrom; +import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedApexImage; import static com.android.tools.build.bundletool.testing.TargetingUtils.textureCompressionTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredBuilderPropertyException; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredFlagException; @@ -40,7 +44,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.aapt.ConfigurationOuterClass.Configuration; import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlNode; import com.android.bundle.Config.BundleConfig; @@ -52,19 +55,24 @@ import com.android.bundle.Files.TargetedAssetsDirectory; import com.android.bundle.Files.TargetedNativeDirectory; import com.android.bundle.Targeting.Abi; +import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.AssetsDirectoryTargeting; import com.android.bundle.Targeting.NativeDirectoryTargeting; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.model.AppBundle; 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.android.tools.build.bundletool.testing.BundleConfigBuilder; +import com.android.tools.build.bundletool.testing.FileUtils; +import com.android.tools.build.bundletool.testing.ManifestProtoUtils.ManifestMutator; import com.android.tools.build.bundletool.testing.ResourceTableBuilder; -import com.android.tools.build.bundletool.utils.flags.FlagParser; -import com.android.tools.build.bundletool.version.BundleToolVersion; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.common.truth.Truth; import java.io.IOException; import java.nio.file.Files; @@ -199,8 +207,10 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalUncompressed() throw public void validModule() throws Exception { XmlNode manifest = androidManifest(PKG_NAME, withHasCode(true)); ResourceTable resourceTable = - createResourceTable( - "icon", fileReference("res/drawable/icon.png", Configuration.getDefaultInstance())); + new ResourceTableBuilder() + .addPackage(PKG_NAME) + .addDrawableResource("icon", "res/drawable/icon.png") + .build(); Path module = new ZipBuilder() .addFileWithContent(ZipPath.create("assets/anything.dat"), "any".getBytes(UTF_8)) @@ -234,16 +244,20 @@ public void validModule() throws Exception { @Test public void validApexModule() throws Exception { XmlNode manifest = androidManifest(PKG_NAME, withHasCode(false)); + ImmutableSet targetedAbis = ImmutableSet.of(X86_64, X86, ARM64_V8A, ARMEABI_V7A); ApexImages apexConfig = - ApexImages.newBuilder() - .addImage( - TargetedApexImage.newBuilder() - .setPath("apex/x86.img") - .setTargeting(apexImageTargeting("x86"))) - .build(); + apexImages( + targetedImageWithAlternatives("apex/x86_64.img", X86_64, targetedAbis), + targetedImageWithAlternatives("apex/x86.img", X86, targetedAbis), + targetedImageWithAlternatives("apex/arm64-v8a.img", ARM64_V8A, targetedAbis), + targetedImageWithAlternatives("apex/armeabi-v7a.img", ARMEABI_V7A, targetedAbis)); Path module = new ZipBuilder() - .addFileWithContent(ZipPath.create("apex/x86.img"), "apex".getBytes(UTF_8)) + .addFileWithContent(ZipPath.create("apex/x86_64.img"), "x86_64".getBytes(UTF_8)) + .addFileWithContent(ZipPath.create("apex/x86.img"), "x86".getBytes(UTF_8)) + .addFileWithContent(ZipPath.create("apex/arm64-v8a.img"), "arm64-v8a".getBytes(UTF_8)) + .addFileWithContent( + ZipPath.create("apex/armeabi-v7a.img"), "armeabi-v7a".getBytes(UTF_8)) .addFileWithProtoContent(ZipPath.create("manifest/AndroidManifest.xml"), manifest) .addFileWithContent( ZipPath.create("root/apex_manifest.json"), "manifest".getBytes(UTF_8)) @@ -259,7 +273,12 @@ public void validApexModule() throws Exception { assertThat(bundle) .hasFile("base/manifest/AndroidManifest.xml") .withContent(manifest.toByteArray()); - assertThat(bundle).hasFile("base/apex/x86.img").withContent("apex".getBytes(UTF_8)); + assertThat(bundle).hasFile("base/apex/x86_64.img").withContent("x86_64".getBytes(UTF_8)); + assertThat(bundle).hasFile("base/apex/x86.img").withContent("x86".getBytes(UTF_8)); + assertThat(bundle).hasFile("base/apex/arm64-v8a.img").withContent("arm64-v8a".getBytes(UTF_8)); + assertThat(bundle) + .hasFile("base/apex/armeabi-v7a.img") + .withContent("armeabi-v7a".getBytes(UTF_8)); assertThat(bundle) .hasFile("base/root/apex_manifest.json") .withContent("manifest".getBytes(UTF_8)); @@ -610,22 +629,23 @@ public void moduleWithWrongExtension_throws() throws Exception { @Test public void duplicateModules_throws() throws Exception { - Path moduleInDirA = tmp.newFolder("a").toPath().resolve("module.zip"); - new ZipBuilder().writeTo(moduleInDirA); - Path moduleInDirB = tmp.newFolder("b").toPath().resolve("module.zip"); - new ZipBuilder().writeTo(moduleInDirB); + Path moduleBase = buildSimpleModule("base"); + Path moduleFeature1 = buildSimpleModule("feature"); + Path moduleFeature2 = buildSimpleModule("feature"); - IllegalArgumentException exception = + ValidationException exception = assertThrows( - IllegalArgumentException.class, + ValidationException.class, () -> BuildBundleCommand.builder() .setOutputPath(bundlePath) - .setModulesPaths(ImmutableList.of(moduleInDirA, moduleInDirB)) + .setModulesPaths(ImmutableList.of(moduleBase, moduleFeature1, moduleFeature2)) .build() .execute()); - assertThat(exception).hasMessageThat().contains("must have unique filenames"); + assertThat(exception) + .hasMessageThat() + .contains("More than one module have the 'split' attribute set to 'feature'"); } @Test @@ -766,6 +786,7 @@ public void runsModuleDependencyValidator_cycle_throws() throws Exception { ZipPath.create("manifest/AndroidManifest.xml"), androidManifest( PKG_NAME, + withSplitId("module1"), withUsesSplit("module2"), withOnDemandAttribute(false), withFusingAttribute(true))) @@ -776,6 +797,7 @@ public void runsModuleDependencyValidator_cycle_throws() throws Exception { ZipPath.create("manifest/AndroidManifest.xml"), androidManifest( PKG_NAME, + withSplitId("module2"), withUsesSplit("module3"), withOnDemandAttribute(false), withFusingAttribute(true))) @@ -786,6 +808,7 @@ public void runsModuleDependencyValidator_cycle_throws() throws Exception { ZipPath.create("manifest/AndroidManifest.xml"), androidManifest( PKG_NAME, + withSplitId("module3"), withUsesSplit("module1"), withOnDemandAttribute(false), withFusingAttribute(true))) @@ -808,8 +831,10 @@ public void runsResourceTableValidator_resourceTableReferencingNonExistingFile_t throws Exception { ResourceTable resourceTable = - createResourceTable( - "icon", fileReference("res/drawable/icon.png", Configuration.getDefaultInstance())); + new ResourceTableBuilder() + .addPackage(PKG_NAME) + .addDrawableResource("icon", "res/drawable/icon.png") + .build(); Path module = new ZipBuilder() .addFileWithProtoContent(ZipPath.create("resources.pb"), resourceTable) @@ -893,9 +918,23 @@ private Path createSimpleBaseModule() throws IOException { } private Path buildSimpleModule(String moduleName) throws IOException { + ManifestMutator[] manifestMutators = + moduleName.equals("base") + ? new ManifestMutator[0] + : new ManifestMutator[] {withSplitId(moduleName)}; + return new ZipBuilder() .addFileWithProtoContent( - ZipPath.create("manifest/AndroidManifest.xml"), androidManifest(PKG_NAME)) - .writeTo(tmpDir.resolve(moduleName + ".zip")); + ZipPath.create("manifest/AndroidManifest.xml"), + androidManifest(PKG_NAME, manifestMutators)) + .writeTo(FileUtils.getRandomFilePath(tmp, moduleName, ".zip")); + } + + private TargetedApexImage targetedImageWithAlternatives( + String path, AbiAlias abi, ImmutableSet targetedAbis) { + return targetedApexImage( + path, + multiAbiTargeting( + abi, Sets.difference(targetedAbis, ImmutableSet.of(abi)).immutableCopy())); } } diff --git a/src/test/java/com/android/tools/build/bundletool/commands/CommandHelpTest.java b/src/test/java/com/android/tools/build/bundletool/commands/CommandHelpTest.java index 3654d9e0..d379d07d 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/CommandHelpTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/CommandHelpTest.java @@ -20,6 +20,7 @@ import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription; import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.Arrays; @@ -69,6 +70,7 @@ public void printHelp_correctStructureAndIndentation() { CommandHelp.builder() .setCommandName("MyCommandName") + .setSubCommandNames(ImmutableList.of("SubCommand1", "SubCommand2")) .setCommandDescription( CommandDescription.builder() .setShortDescription( @@ -108,7 +110,7 @@ public void printHelp_correctStructureAndIndentation() { " help of the command.", "", "Synopsis:", - " bundletool MyCommandName", + " bundletool MyCommandName ", " --foo=", " [--bar]", "", diff --git a/src/test/java/com/android/tools/build/bundletool/commands/DebugKeystoreUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/commands/DebugKeystoreUtilsTest.java new file mode 100755 index 00000000..097b41fe --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/commands/DebugKeystoreUtilsTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 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.commands; + +import static com.google.common.base.StandardSystemProperty.USER_HOME; +import static com.google.common.truth.Truth8.assertThat; + +import com.android.tools.build.bundletool.model.utils.files.FileUtils; +import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; +import com.google.common.collect.ImmutableMap; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DebugKeystoreUtilsTest { + + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + + private Path tmpDir; + + @Before + public void setUp() throws Exception { + tmpDir = tmp.newFolder().toPath(); + } + + @Test + public void debugKeystore_notFound() throws Exception { + Path androidSdkHome = tmpDir.resolve("androidSdkHome").resolve(".android"); + Files.createDirectories(androidSdkHome); + + Path homeDir = tmpDir.resolve("home"); + Files.createDirectories(homeDir); + + FakeSystemEnvironmentProvider fakeEnvironmentVariableProvider = + new FakeSystemEnvironmentProvider( + /* variables= */ ImmutableMap.of( + "ANDROID_SDK_HOME", tmpDir.resolve("androidSdkHome").toString(), + "HOME", homeDir.toString()), + /* properties= */ ImmutableMap.of(USER_HOME.key(), homeDir.toString())); + + assertThat(DebugKeystoreUtils.DEBUG_KEYSTORE_CACHE.get(fakeEnvironmentVariableProvider)) + .isEmpty(); + } + + @Test + public void debugKeyStore_prefersAndroidSdkHome() throws Exception { + Path androidSdkHome = tmpDir.resolve("androidSdkHome"); + Path androidSdkHomeKey = androidSdkHome.resolve(".android").resolve("debug.keystore"); + FileUtils.createParentDirectories(androidSdkHomeKey); + Files.write(androidSdkHomeKey, new byte[0]); + + Path homeDir = tmpDir.resolve("home"); + Path homeDebugKey = homeDir.resolve(".android").resolve("debug.keystore"); + FileUtils.createParentDirectories(homeDebugKey); + Files.write(homeDebugKey, new byte[0]); + + FakeSystemEnvironmentProvider fakeEnvironmentVariableProvider = + new FakeSystemEnvironmentProvider( + /* variables= */ ImmutableMap.of( + "ANDROID_SDK_HOME", androidSdkHome.toString(), + "HOME", homeDir.toString()), + /* properties= */ ImmutableMap.of(USER_HOME.key(), homeDir.toString())); + + assertThat(DebugKeystoreUtils.DEBUG_KEYSTORE_CACHE.get(fakeEnvironmentVariableProvider)) + .hasValue(androidSdkHomeKey); + } + + @Test + public void debugKeystore_userHome_secondPreference() throws Exception { + Path androidSdkHome = tmpDir.resolve("androidSdkHome"); + Files.createDirectories(androidSdkHome); + + Path homeDir = tmpDir.resolve("home"); + Path homeDebugKey = homeDir.resolve(".android").resolve("debug.keystore"); + FileUtils.createParentDirectories(homeDebugKey); + Files.write(homeDebugKey, new byte[0]); + + Path homeEnvDir = tmpDir.resolve("homeEnv"); + Path homeEnvDebugKey = homeEnvDir.resolve(".android").resolve("debug.keystore"); + FileUtils.createParentDirectories(homeEnvDebugKey); + Files.write(homeEnvDebugKey, new byte[0]); + + FakeSystemEnvironmentProvider fakeEnvironmentVariableProvider = + new FakeSystemEnvironmentProvider( + /* variables= */ ImmutableMap.of( + "ANDROID_SDK_HOME", androidSdkHome.toString(), + "HOME", homeEnvDir.toString()), + /* properties= */ ImmutableMap.of(USER_HOME.key(), homeDir.toString())); + + assertThat(DebugKeystoreUtils.DEBUG_KEYSTORE_CACHE.get(fakeEnvironmentVariableProvider)) + .hasValue(homeDebugKey); + } + + @Test + public void debugKeystore_homeEnvironmentVariable_thirdPreference() throws Exception { + Path androidSdkHome = tmpDir.resolve("androidSdkHome"); + Files.createDirectories(androidSdkHome); + + Path homeDir = tmpDir.resolve("home"); + Files.createDirectories(homeDir); + + Path homeEnvDir = tmpDir.resolve("homeEnv"); + Path homeEnvDebugKey = homeEnvDir.resolve(".android").resolve("debug.keystore"); + FileUtils.createParentDirectories(homeEnvDebugKey); + Files.write(homeEnvDebugKey, new byte[0]); + + FakeSystemEnvironmentProvider fakeEnvironmentVariableProvider = + new FakeSystemEnvironmentProvider( + /* variables= */ ImmutableMap.of( + "ANDROID_SDK_HOME", androidSdkHome.toString(), + "HOME", homeEnvDir.toString()), + /* properties= */ ImmutableMap.of(USER_HOME.key(), homeDir.toString())); + + assertThat(DebugKeystoreUtils.DEBUG_KEYSTORE_CACHE.get(fakeEnvironmentVariableProvider)) + .hasValue(homeEnvDebugKey); + } +} diff --git a/src/test/java/com/android/tools/build/bundletool/commands/DumpCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/DumpCommandTest.java index 2400b045..ea89df92 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/DumpCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/DumpCommandTest.java @@ -21,11 +21,11 @@ import com.android.aapt.Resources.ResourceTable; import com.android.tools.build.bundletool.commands.DumpCommand.DumpTarget; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; import com.android.tools.build.bundletool.io.AppBundleSerializer; import com.android.tools.build.bundletool.model.AppBundle; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.AppBundleBuilder; -import com.android.tools.build.bundletool.utils.flags.FlagParser; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/test/java/com/android/tools/build/bundletool/commands/DumpManagerTest.java b/src/test/java/com/android/tools/build/bundletool/commands/DumpManagerTest.java index 7346679e..bab309a6 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/DumpManagerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/DumpManagerTest.java @@ -112,7 +112,7 @@ public void dumpManifest_moduleNotBase() throws Exception { .execute(); assertThat(new String(outputStream.toByteArray(), UTF_8)) - .isEqualTo(String.format("%n")); + .isEqualTo(String.format("%n")); } @Test @@ -417,9 +417,50 @@ public void printResources_withValues() throws Exception { String.format( "Package 'com.app':%n" + "0x7F010000 - string/title%n" - + "\t(default) - Title [STR]%n" + + "\t(default) - [STR] \"Title\"%n" + "0x7F020000 - drawable/icon%n" - + "\t(default) - res/drawable/icon.png [FILE]%n" + + "\t(default) - [FILE] res/drawable/icon.png%n" + + "%n")); + } + + @Test + public void printResources_valuesEscaped() throws Exception { + createBundleWithResourceTable( + bundlePath, + new ResourceTableBuilder() + .addPackage("com.app") + .addStringResource("text", "First line\nSecond line\nThird line") + .addStringResource("text2", "First line\r\nSecond line\r\nThird line") + .addStringResource("text3", "First line\u2028Second line\u2028Third line") + .addStringResource("text4", "First line\\nSame line!") + .addStringResource("text5", "Text \"with\" quotes!") + .build()); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + DumpCommand.builder() + .setBundlePath(bundlePath) + .setDumpTarget(DumpTarget.RESOURCES) + .setOutputStream(new PrintStream(outputStream)) + .setPrintValues(true) + .build() + .execute(); + + String output = new String(outputStream.toByteArray(), UTF_8); + assertThat(output) + .isEqualTo( + String.format( + "Package 'com.app':%n" + + "0x7F010000 - string/text%n" + + "\t(default) - [STR] \"First line\\nSecond line\\nThird line\"%n" + + "0x7F010001 - string/text2%n" + + "\t(default) - [STR] \"First line\\r\\nSecond line\\r\\nThird line\"%n" + + "0x7F010002 - string/text3%n" + + "\t(default) - [STR] \"First line\\u2028Second line\\u2028Third line\"%n" + + "0x7F010003 - string/text4%n" + + "\t(default) - [STR] \"First line\\\\nSame line!\"%n" + + "0x7F010004 - string/text5%n" + + "\t(default) - [STR] \"Text \\\"with\\\" quotes!\"%n" + "%n")); } 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 2b1ca40f..bdcee81f 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 @@ -19,6 +19,8 @@ import static com.android.bundle.Targeting.Abi.AbiAlias.ARM64_V8A; import static com.android.bundle.Targeting.Abi.AbiAlias.X86; import static com.android.bundle.Targeting.Abi.AbiAlias.X86_64; +import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.XXHDPI; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApkDescription; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApksArchiveFile; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApksDirectory; @@ -39,6 +41,7 @@ import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; import static com.android.tools.build.bundletool.testing.TargetingUtils.apkAbiTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.apkDensityTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeModuleTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleFeatureTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleMinSdkVersionTargeting; @@ -46,7 +49,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.sdkVersionFrom; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredFlagException; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -60,10 +62,10 @@ import com.android.bundle.Targeting.SdkVersion; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.flags.FlagParser; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -74,6 +76,7 @@ import java.io.FileOutputStream; import java.io.PrintStream; import java.io.Reader; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.Before; @@ -1199,6 +1202,22 @@ public void extractInstant_withMultipleInstantModule() throws Exception { } } + @Test + public void testExtractFromDirectoryNoTableOfContents_throws() throws Exception { + Files.createFile(tmpDir.resolve("base-master.apk")); + Exception e = + assertThrows( + IllegalArgumentException.class, + () -> + ExtractApksCommand.builder() + .setApksArchivePath(tmpDir) + .setDeviceSpec(DeviceSpec.getDefaultInstance()) + .build() + .execute()); + + assertThat(e).hasMessageThat().matches("File '.*toc.pb' was not found."); + } + private Path createApks(BuildApksResult buildApksResult, boolean apksInDirectory) throws Exception { diff --git a/src/test/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommandTest.java index 2853f2b3..79d18ff5 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommandTest.java @@ -22,6 +22,8 @@ import static com.android.tools.build.bundletool.testing.DeviceFactory.locales; import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; +import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_HOME; +import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_SERIAL; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredBuilderPropertyException; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredFlagException; import static com.google.common.truth.Truth.assertThat; @@ -31,15 +33,15 @@ import com.android.bundle.Devices.DeviceSpec; import com.android.ddmlib.IDevice.DeviceState; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.android.tools.build.bundletool.testing.FakeAdbServer; -import com.android.tools.build.bundletool.testing.FakeAndroidHomeVariableProvider; -import com.android.tools.build.bundletool.testing.FakeAndroidSerialVariableProvider; import com.android.tools.build.bundletool.testing.FakeDevice; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.files.BufferedIo; -import com.android.tools.build.bundletool.utils.flags.FlagParser; +import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.protobuf.util.JsonFormat; import java.io.IOException; import java.io.Reader; @@ -60,8 +62,7 @@ public class GetDeviceSpecCommandTest { private Path tmpDir; private static final String DEVICE_ID = "id1"; - private EnvironmentVariableProvider androidHomeProvider; - private EnvironmentVariableProvider androidSerialProvider; + private SystemEnvironmentProvider systemEnvironmentProvider; private Path adbPath; private Path sdkDirPath; @@ -74,8 +75,9 @@ public void setUp() throws IOException { Files.createFile(adbPath); adbPath.toFile().setExecutable(true); - this.androidHomeProvider = new FakeAndroidHomeVariableProvider(sdkDirPath.toString()); - this.androidSerialProvider = new FakeAndroidSerialVariableProvider(DEVICE_ID); + this.systemEnvironmentProvider = + new FakeSystemEnvironmentProvider( + ImmutableMap.of(ANDROID_HOME, sdkDirPath.toString(), ANDROID_SERIAL, DEVICE_ID)); } @Test @@ -85,7 +87,7 @@ public void fromFlagsEquivalentToBuilder_onlyOutputPath() { GetDeviceSpecCommand commandViaFlags = GetDeviceSpecCommand.fromFlags( new FlagParser().parse("--output=" + outputPath), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); // The command via flags uses $ANDROID_HOME as a base for ADB location if the --adb flag is @@ -97,6 +99,7 @@ public void fromFlagsEquivalentToBuilder_onlyOutputPath() { .setAdbPath(sdkDirPath.resolve("platform-tools").resolve("adb")) // it's impractical not to copy. .setAdbServer(commandViaFlags.getAdbServer()) + .setDeviceId(DEVICE_ID) .build(); assertThat(commandViaFlags).isEqualTo(commandViaBuilder); @@ -104,6 +107,8 @@ public void fromFlagsEquivalentToBuilder_onlyOutputPath() { @Test public void fromFlagsEquivalentToBuilder_noDeviceId() { + SystemEnvironmentProvider androidHomeProvider = + new FakeSystemEnvironmentProvider(ImmutableMap.of(ANDROID_HOME, sdkDirPath.toString())); Path outputPath = tmpDir.resolve("device.json"); GetDeviceSpecCommand commandViaFlags = @@ -130,7 +135,7 @@ public void fromFlagsEquivalentToBuilder_allFlagsUsed() { GetDeviceSpecCommand.fromFlags( new FlagParser() .parse("--adb=" + adbPath, "--device-id=" + DEVICE_ID, "--output=" + outputPath), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); GetDeviceSpecCommand commandViaBuilder = @@ -156,7 +161,7 @@ public void fromFlagsEquivalentToBuilder_allFlagsUsed_overwrite() { "--device-id=" + DEVICE_ID, "--output=" + outputPath, "--overwrite"), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); GetDeviceSpecCommand commandViaBuilder = @@ -178,7 +183,7 @@ public void fromFlagsEquivalentToBuilder_withEnvironmentalVariables() { GetDeviceSpecCommand commandViaFlags = GetDeviceSpecCommand.fromFlags( new FlagParser().parse("--adb=" + adbPath, "--output=" + outputPath), - androidSerialProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); GetDeviceSpecCommand commandViaBuilder = @@ -202,7 +207,7 @@ public void missingOuputFlag_fails() { () -> GetDeviceSpecCommand.fromFlags( new FlagParser().parse("--adb=" + adbPath), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US")))); } 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 4b7a2cbe..5baf3fa6 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 @@ -26,8 +26,10 @@ import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.HDPI; import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.LDPI; import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.MDPI; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.ALL; import static com.android.tools.build.bundletool.commands.GetSizeCommand.SUPPORTED_DIMENSIONS; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.ALL; +import static com.android.tools.build.bundletool.model.utils.CsvFormatter.CRLF; +import static com.android.tools.build.bundletool.model.utils.ZipUtils.calculateGzipCompressedSize; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApkDescription; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApksArchiveFile; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createInstantApkSet; @@ -47,8 +49,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.variantDensityTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredFlagException; -import static com.android.tools.build.bundletool.utils.CsvFormatter.CRLF; -import static com.android.tools.build.bundletool.utils.ZipUtils.calculateGzipCompressedSize; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -60,18 +60,18 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.SdkVersion; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension; import com.android.tools.build.bundletool.commands.GetSizeCommand.GetSizeSubcommand; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.flags.FlagParser.FlagParseException; +import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.io.ZipBuilder.EntryOption; import com.android.tools.build.bundletool.model.ConfigurationSizes; +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.utils.flags.FlagParser; -import com.android.tools.build.bundletool.utils.flags.FlagParser.FlagParseException; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; 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 4ab7a550..2c943c10 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 @@ -29,6 +29,8 @@ import static com.android.tools.build.bundletool.testing.DeviceFactory.locales; import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; +import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_HOME; +import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_SERIAL; import static com.android.tools.build.bundletool.testing.TargetingUtils.apkAbiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.sdkVersionFrom; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; @@ -45,16 +47,16 @@ import com.android.bundle.Targeting.VariantTargeting; import com.android.ddmlib.IDevice.DeviceState; import com.android.tools.build.bundletool.device.AdbServer; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.InstallationException; +import com.android.tools.build.bundletool.flags.FlagParser; 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.InstallationException; +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; import com.android.tools.build.bundletool.testing.FakeAdbServer; -import com.android.tools.build.bundletool.testing.FakeAndroidHomeVariableProvider; -import com.android.tools.build.bundletool.testing.FakeAndroidSerialVariableProvider; import com.android.tools.build.bundletool.testing.FakeDevice; -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import com.android.tools.build.bundletool.utils.flags.FlagParser; +import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.io.IOException; @@ -82,8 +84,7 @@ public class InstallApksCommandTest { private Path tmpDir; - private EnvironmentVariableProvider androidHomeProvider; - private EnvironmentVariableProvider androidSerialProvider; + private SystemEnvironmentProvider systemEnvironmentProvider; private Path adbPath; private Path sdkDirPath; @@ -95,8 +96,9 @@ public void setUp() throws IOException { Files.createDirectories(adbPath.getParent()); Files.createFile(adbPath); adbPath.toFile().setExecutable(true); - this.androidHomeProvider = new FakeAndroidHomeVariableProvider(sdkDirPath.toString()); - this.androidSerialProvider = new FakeAndroidSerialVariableProvider(DEVICE_ID); + this.systemEnvironmentProvider = + new FakeSystemEnvironmentProvider( + ImmutableMap.of(ANDROID_HOME, sdkDirPath.toString(), ANDROID_SERIAL, DEVICE_ID)); } @Test @@ -107,7 +109,7 @@ public void fromFlagsEquivalentToBuilder_onlyApkPaths() throws Exception { InstallApksCommand fromFlags = InstallApksCommand.fromFlags( new FlagParser().parse("--apks=" + apksFile), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); InstallApksCommand fromBuilder = @@ -115,6 +117,7 @@ public void fromFlagsEquivalentToBuilder_onlyApkPaths() throws Exception { .setApksArchivePath(apksFile) .setAdbPath(adbPath) .setAdbServer(fromFlags.getAdbServer()) + .setDeviceId(DEVICE_ID) .build(); assertThat(fromBuilder).isEqualTo(fromFlags); @@ -128,7 +131,7 @@ public void fromFlagsEquivalentToBuilder_apkPathsAndAdb() throws Exception { InstallApksCommand fromFlags = InstallApksCommand.fromFlags( new FlagParser().parse("--apks=" + apksFile, "--adb=" + adbPath), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); InstallApksCommand fromBuilder = @@ -136,6 +139,7 @@ public void fromFlagsEquivalentToBuilder_apkPathsAndAdb() throws Exception { .setApksArchivePath(apksFile) .setAdbPath(adbPath) .setAdbServer(fromFlags.getAdbServer()) + .setDeviceId(DEVICE_ID) .build(); assertThat(fromBuilder).isEqualTo(fromFlags); @@ -150,7 +154,7 @@ public void fromFlagsEquivalentToBuilder_deviceId() throws Exception { InstallApksCommand.fromFlags( new FlagParser() .parse("--apks=" + apksFile, "--adb=" + adbPath, "--device-id=" + DEVICE_ID), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); InstallApksCommand fromBuilder = @@ -172,7 +176,7 @@ public void fromFlagsEquivalentToBuilder_allowDowngrade() throws Exception { InstallApksCommand fromFlags = InstallApksCommand.fromFlags( new FlagParser().parse("--apks=" + apksFile, "--allow-downgrade"), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); InstallApksCommand fromBuilder = @@ -180,6 +184,7 @@ public void fromFlagsEquivalentToBuilder_allowDowngrade() throws Exception { .setApksArchivePath(apksFile) .setAdbPath(adbPath) .setAdbServer(fromFlags.getAdbServer()) + .setDeviceId(DEVICE_ID) .setAllowDowngrade(true) .build(); @@ -194,7 +199,7 @@ public void fromFlagsEquivalentToBuilder_androidSerialVariable() throws Exceptio InstallApksCommand fromFlags = InstallApksCommand.fromFlags( new FlagParser().parse("--apks=" + apksFile, "--adb=" + adbPath), - androidSerialProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); InstallApksCommand fromBuilder = @@ -217,7 +222,7 @@ public void fromFlagsEquivalentToBuilder_modules() throws Exception { InstallApksCommand.fromFlags( new FlagParser() .parse("--apks=" + apksFile, "--adb=" + adbPath, "--modules=base,feature"), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US"))); InstallApksCommand fromBuilder = @@ -226,6 +231,7 @@ public void fromFlagsEquivalentToBuilder_modules() throws Exception { .setAdbPath(adbPath) .setAdbServer(fromFlags.getAdbServer()) .setModules(ImmutableSet.of("base", "feature")) + .setDeviceId(DEVICE_ID) .build(); assertThat(fromBuilder).isEqualTo(fromFlags); @@ -246,7 +252,7 @@ public void missingApksFlag_fails() { () -> InstallApksCommand.fromFlags( new FlagParser().parse("--adb=" + adbPath), - androidHomeProvider, + systemEnvironmentProvider, fakeServerOneDevice(lDeviceWithLocales("en-US")))); } diff --git a/src/test/java/com/android/tools/build/bundletool/commands/ValidateBundleCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/ValidateBundleCommandTest.java index 80df4763..ccf12c3e 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/ValidateBundleCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/ValidateBundleCommandTest.java @@ -21,8 +21,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.utils.flags.FlagParser; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.flags.ParsedFlags; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; diff --git a/src/test/java/com/android/tools/build/bundletool/commands/VersionCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/VersionCommandTest.java index 46ba1c6e..548c7b46 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/VersionCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/VersionCommandTest.java @@ -18,8 +18,8 @@ import static com.google.common.truth.Truth.assertThat; -import com.android.tools.build.bundletool.utils.flags.FlagParser; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.flags.FlagParser; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.base.Splitter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; diff --git a/src/test/java/com/android/tools/build/bundletool/device/AbiMatcherTest.java b/src/test/java/com/android/tools/build/bundletool/device/AbiMatcherTest.java index 04bdbf86..f4fa7ff9 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/AbiMatcherTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/AbiMatcherTest.java @@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Targeting.Abi.AbiAlias; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/device/AdbShellCommandTaskTest.java b/src/test/java/com/android/tools/build/bundletool/device/AdbShellCommandTaskTest.java index 10c907bf..7f0efb06 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/AdbShellCommandTaskTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/AdbShellCommandTaskTest.java @@ -24,7 +24,7 @@ import com.android.ddmlib.IDevice.DeviceState; import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.TimeoutException; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.testing.FakeDevice; import java.io.IOException; import org.junit.Test; 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 e0005a26..7de67cbd 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 @@ -71,14 +71,14 @@ import com.android.bundle.Targeting.MultiAbiTargeting; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AndroidManifest; 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.ZipPath; -import com.android.tools.build.bundletool.utils.Versions; +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.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Arrays; diff --git a/src/test/java/com/android/tools/build/bundletool/device/ApksInstallerTest.java b/src/test/java/com/android/tools/build/bundletool/device/ApksInstallerTest.java index f432e466..b88fe291 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/ApksInstallerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/ApksInstallerTest.java @@ -22,8 +22,8 @@ import com.android.ddmlib.IDevice.DeviceState; import com.android.tools.build.bundletool.device.Device.InstallOptions; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.exceptions.InstallationException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.InstallationException; import com.android.tools.build.bundletool.testing.FakeAdbServer; import com.android.tools.build.bundletool.testing.FakeDevice; import com.google.common.collect.ImmutableList; diff --git a/src/test/java/com/android/tools/build/bundletool/device/DeviceAnalyzerTest.java b/src/test/java/com/android/tools/build/bundletool/device/DeviceAnalyzerTest.java index 97764425..22db8cf4 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/DeviceAnalyzerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/DeviceAnalyzerTest.java @@ -27,7 +27,7 @@ import com.android.bundle.Devices.DeviceSpec; import com.android.ddmlib.IDevice.DeviceState; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.testing.FakeAdbServer; import com.android.tools.build.bundletool.testing.FakeDevice; import com.google.common.base.Joiner; diff --git a/src/test/java/com/android/tools/build/bundletool/device/DeviceFeaturesParserTest.java b/src/test/java/com/android/tools/build/bundletool/device/DeviceFeaturesParserTest.java index 02b583cd..1515ab08 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/DeviceFeaturesParserTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/DeviceFeaturesParserTest.java @@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.ParseException; +import com.android.tools.build.bundletool.model.exceptions.ParseException; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/device/DeviceSpecParserTest.java b/src/test/java/com/android/tools/build/bundletool/device/DeviceSpecParserTest.java index 948ed739..f63da23d 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/DeviceSpecParserTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/DeviceSpecParserTest.java @@ -21,7 +21,7 @@ import com.android.bundle.Devices.DeviceSpec; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import java.nio.file.Paths; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/DeviceSpecUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/device/DeviceSpecUtilsTest.java similarity index 97% rename from src/test/java/com/android/tools/build/bundletool/utils/DeviceSpecUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/device/DeviceSpecUtilsTest.java index e4796ec6..a2acdb65 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/DeviceSpecUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/DeviceSpecUtilsTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.device; import static com.android.bundle.Targeting.Abi.AbiAlias.ARM64_V8A; import static com.android.bundle.Targeting.Abi.AbiAlias.ARMEABI; @@ -42,7 +42,7 @@ import com.android.bundle.Targeting.LanguageTargeting; import com.android.bundle.Targeting.ScreenDensityTargeting; import com.android.bundle.Targeting.SdkVersionTargeting; -import com.android.tools.build.bundletool.utils.DeviceSpecUtils.DeviceSpecFromTargetingBuilder; +import com.android.tools.build.bundletool.device.DeviceSpecUtils.DeviceSpecFromTargetingBuilder; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/device/MultiAbiMatcherTest.java b/src/test/java/com/android/tools/build/bundletool/device/MultiAbiMatcherTest.java index c0198ca3..34962708 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/MultiAbiMatcherTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/MultiAbiMatcherTest.java @@ -26,7 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Targeting.MultiAbiTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/device/ScreenDensityMatcherTest.java b/src/test/java/com/android/tools/build/bundletool/device/ScreenDensityMatcherTest.java index 203e3caa..9e5ce721 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/ScreenDensityMatcherTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/ScreenDensityMatcherTest.java @@ -16,9 +16,9 @@ package com.android.tools.build.bundletool.device; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; import static com.android.tools.build.bundletool.testing.DeviceFactory.lDeviceWithDensity; import static com.android.tools.build.bundletool.testing.TargetingUtils.screenDensityTargeting; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; import static com.google.common.truth.Truth.assertThat; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; diff --git a/src/test/java/com/android/tools/build/bundletool/device/SdkVersionMatcherTest.java b/src/test/java/com/android/tools/build/bundletool/device/SdkVersionMatcherTest.java index 6073bed9..9e118877 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/SdkVersionMatcherTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/SdkVersionMatcherTest.java @@ -23,7 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Targeting.SdkVersion; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/device/VariantMatcherTest.java b/src/test/java/com/android/tools/build/bundletool/device/VariantMatcherTest.java index 53b28d05..81784e63 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/VariantMatcherTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/VariantMatcherTest.java @@ -50,8 +50,8 @@ import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.MultiAbiTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Test; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/VariantTotalSizeAggregatorTest.java b/src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/VariantTotalSizeAggregatorTest.java rename to src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java index 96c9e66e..c357afcb 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/VariantTotalSizeAggregatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.device; import static com.android.bundle.Targeting.Abi.AbiAlias.ARM64_V8A; import static com.android.bundle.Targeting.Abi.AbiAlias.ARMEABI; @@ -28,10 +28,10 @@ import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.XHDPI; import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.XXHDPI; import static com.android.bundle.Targeting.ScreenDensity.DensityAlias.XXXHDPI; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.ABI; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.LANGUAGE; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.SCREEN_DENSITY; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.SDK; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.ABI; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.LANGUAGE; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.SCREEN_DENSITY; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.SDK; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApkDescription; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createInstantApkSet; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createMasterApkDescription; @@ -60,10 +60,10 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.tools.build.bundletool.commands.GetSizeCommand; import com.android.tools.build.bundletool.commands.GetSizeCommand.GetSizeSubcommand; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.ConfigurationSizes; 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.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/flags/FlagParserTest.java b/src/test/java/com/android/tools/build/bundletool/flags/FlagParserTest.java similarity index 97% rename from src/test/java/com/android/tools/build/bundletool/utils/flags/FlagParserTest.java rename to src/test/java/com/android/tools/build/bundletool/flags/FlagParserTest.java index 7af34294..f6ae20e4 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/flags/FlagParserTest.java +++ b/src/test/java/com/android/tools/build/bundletool/flags/FlagParserTest.java @@ -14,15 +14,15 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.flags; +package com.android.tools.build.bundletool.flags; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.utils.OsPlatform; -import com.android.tools.build.bundletool.utils.flags.Flag.RequiredFlagNotSetException; -import com.android.tools.build.bundletool.utils.flags.FlagParser.FlagParseException; -import com.android.tools.build.bundletool.utils.flags.ParsedFlags.UnknownFlagsException; +import com.android.tools.build.bundletool.flags.Flag.RequiredFlagNotSetException; +import com.android.tools.build.bundletool.flags.FlagParser.FlagParseException; +import com.android.tools.build.bundletool.flags.ParsedFlags.UnknownFlagsException; +import com.android.tools.build.bundletool.model.utils.OsPlatform; import java.nio.file.Paths; import java.util.Optional; import org.junit.Test; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/flags/FlagTest.java b/src/test/java/com/android/tools/build/bundletool/flags/FlagTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/flags/FlagTest.java rename to src/test/java/com/android/tools/build/bundletool/flags/FlagTest.java index 417048d3..f228baab 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/flags/FlagTest.java +++ b/src/test/java/com/android/tools/build/bundletool/flags/FlagTest.java @@ -14,14 +14,14 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.flags; +package com.android.tools.build.bundletool.flags; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.utils.flags.Flag.Password; -import com.android.tools.build.bundletool.utils.flags.FlagParser.FlagParseException; +import com.android.tools.build.bundletool.flags.FlagParser.FlagParseException; +import com.android.tools.build.bundletool.model.Password; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; diff --git a/src/test/java/com/android/tools/build/bundletool/mergers/D8DexMergerTest.java b/src/test/java/com/android/tools/build/bundletool/mergers/D8DexMergerTest.java index 68b5592a..f6b555f1 100755 --- a/src/test/java/com/android/tools/build/bundletool/mergers/D8DexMergerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/mergers/D8DexMergerTest.java @@ -16,8 +16,8 @@ package com.android.tools.build.bundletool.mergers; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_K_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_L_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_K_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_L_API_VERSION; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -26,7 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.testing.FileUtils; import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.graph.DexApplication; diff --git a/src/test/java/com/android/tools/build/bundletool/mergers/MergingUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/mergers/MergingUtilsTest.java index d6f3a35e..90f7c33a 100755 --- a/src/test/java/com/android/tools/build/bundletool/mergers/MergingUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/mergers/MergingUtilsTest.java @@ -29,7 +29,7 @@ import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMergerTest.java b/src/test/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMergerTest.java index bd9161d7..78d07cae 100755 --- a/src/test/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMergerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/mergers/ModuleSplitsToShardMergerTest.java @@ -54,7 +54,6 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModuleName; @@ -62,11 +61,12 @@ import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.testing.TestUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -import com.google.common.io.ByteStreams; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -207,10 +207,10 @@ public void dexFiles_allInOneModule_areUnchanged() throws Exception { assertThat(extractPaths(merged.getEntries())) .containsExactly("dex/classes.dex", "dex/classes2.dex"); ModuleEntry classesDexEntry = merged.findEntriesUnderPath("dex/classes.dex").findFirst().get(); - assertThat(ByteStreams.toByteArray(classesDexEntry.getContent())).isEqualTo(classesDexData); + assertThat(TestUtils.getEntryContent(classesDexEntry)).isEqualTo(classesDexData); ModuleEntry classes2DexEntry = merged.findEntriesUnderPath("dex/classes2.dex").findFirst().get(); - assertThat(ByteStreams.toByteArray(classes2DexEntry.getContent())).isEqualTo(classes2DexData); + assertThat(TestUtils.getEntryContent(classes2DexEntry)).isEqualTo(classes2DexData); // No merging means no items in cache. assertThat(dexMergingCache).isEmpty(); } @@ -242,7 +242,7 @@ public void dexFiles_inMultipleModules_areMerged() throws Exception { assertThat(extractPaths(merged.getEntries())).containsExactly("dex/classes.dex"); ModuleEntry mergedDexEntry = merged.findEntriesUnderPath("dex/classes.dex").findFirst().get(); - byte[] mergedDexData = ByteStreams.toByteArray(mergedDexEntry.getContent()); + byte[] mergedDexData = TestUtils.getEntryContent(mergedDexEntry); assertThat(mergedDexData.length).isGreaterThan(0); assertThat(mergedDexData).isNotEqualTo(TestData.readBytes("testdata/dex/classes.dex")); assertThat(mergedDexData).isNotEqualTo(TestData.readBytes("testdata/dex/classes-other.dex")); 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 751d3acc..7e3b3fdf 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 @@ -19,10 +19,14 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.DEBUGGABLE_RESOURCE_ID; 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; import static com.android.tools.build.bundletool.model.AndroidManifest.VALUE_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.VERSION_CODE_RESOURCE_ID; +import static com.android.tools.build.bundletool.model.BundleModule.ModuleType.ASSET_MODULE; +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.withFusingAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withInstallTimeDelivery; @@ -33,6 +37,7 @@ import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withOnDemandAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withOnDemandDelivery; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTargetSandboxVersion; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTypeAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withUsesSplit; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.xmlAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.xmlBooleanAttribute; @@ -48,12 +53,12 @@ import com.android.aapt.Resources.XmlNode; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestFusingException.FusingMissingIncludeAttribute; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestVersionException.VersionCodeMissingException; -import com.android.tools.build.bundletool.utils.xmlproto.UnexpectedAttributeTypeException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestFusingException.FusingMissingIncludeAttribute; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestVersionException.VersionCodeMissingException; +import com.android.tools.build.bundletool.model.utils.xmlproto.UnexpectedAttributeTypeException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableList; import com.google.protobuf.TextFormat; import java.util.Optional; @@ -469,6 +474,39 @@ public void deliveryTypeAndOnDemandAttribute_deliveryElement_conditions() { assertThat(manifest.isDeliveryTypeDeclared()).isTrue(); } + @Test + public void moduleTypeAttribute_assetModule() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest("com.test.app", withTypeAttribute(MODULE_TYPE_ASSET_VALUE))); + + assertThat(manifest.getModuleType()).isPresent(); + assertThat(manifest.getModuleType()).hasValue(ASSET_MODULE); + } + + @Test + public void moduleTypeAttribute_featureModule() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest("com.test.app", withTypeAttribute(MODULE_TYPE_FEATURE_VALUE))); + + assertThat(manifest.getModuleType()).isPresent(); + assertThat(manifest.getModuleType()).hasValue(FEATURE_MODULE); + } + + @Test + public void moduleTypeAttribute_invalid_throws() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest("com.test.app", withTypeAttribute("invalid-attribute"))); + + ValidationException exception = + assertThrows(ValidationException.class, () -> manifest.getModuleType()); + assertThat(exception) + .hasMessageThat() + .contains("Found invalid type attribute invalid-attribute for element."); + } + @Test public void getIsIncludedInFusing_true() { // From Bundletool 0.3.4 onwards. diff --git a/src/test/java/com/android/tools/build/bundletool/model/AppBundleTest.java b/src/test/java/com/android/tools/build/bundletool/model/AppBundleTest.java index 84308f58..eb60cf67 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/AppBundleTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/AppBundleTest.java @@ -17,6 +17,7 @@ package com.android.tools.build.bundletool.model; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTypeAttribute; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries; import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; @@ -33,7 +34,7 @@ import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.testing.AppBundleBuilder; import com.android.tools.build.bundletool.testing.BundleConfigBuilder; -import com.google.common.io.ByteStreams; +import com.android.tools.build.bundletool.testing.TestUtils; import java.nio.file.Path; import java.util.Optional; import java.util.zip.ZipFile; @@ -51,6 +52,8 @@ public class AppBundleTest { private static final byte[] DUMMY_CONTENT = new byte[1]; private static final BundleConfig BUNDLE_CONFIG = BundleConfigBuilder.create().build(); public static final XmlNode MANIFEST = androidManifest("com.test.app.detail"); + public static final XmlNode REMOTE_ASSET_MANIFEST = + androidManifest("com.test.app.detail", withTypeAttribute("remote-asset")); @Rule public TemporaryFolder tmp = new TemporaryFolder(); @@ -68,7 +71,8 @@ public void testSingleModuleBundle() throws Exception { .writeTo(bundleFile); AppBundle appBundle = AppBundle.buildFromZip(new ZipFile(bundleFile.toFile())); - assertThat(appBundle.getModules().keySet()).containsExactly(BundleModuleName.create("base")); + assertThat(appBundle.getFeatureModules().keySet()) + .containsExactly(BundleModuleName.create("base")); } @Test @@ -82,7 +86,7 @@ public void testMultipleModules() throws Exception { .writeTo(bundleFile); AppBundle appBundle = AppBundle.buildFromZip(new ZipFile(bundleFile.toFile())); - assertThat(appBundle.getModules().keySet()) + assertThat(appBundle.getFeatureModules().keySet()) .containsExactly(BundleModuleName.create("base"), BundleModuleName.create("detail")); } @@ -132,8 +136,7 @@ public void bundleMetadataProcessedCorrectly() throws Exception { .getBundleMetadata() .getFileData(/* namespacedDir= */ "some.namespace", /* fileName= */ "metadata1"); assertThat(existingMetadataFile).isPresent(); - assertThat(ByteStreams.toByteArray(existingMetadataFile.get().get())) - .isEqualTo(new byte[] {0x01}); + assertThat(TestUtils.toByteArray(existingMetadataFile.get())).isEqualTo(new byte[] {0x01}); Optional existingMetadataFileInSubDir = appBundle @@ -141,7 +144,7 @@ public void bundleMetadataProcessedCorrectly() throws Exception { .getFileData( /* namespacedDir= */ "some.namespace/sub-dir", /* fileName= */ "metadata2"); assertThat(existingMetadataFileInSubDir).isPresent(); - assertThat(ByteStreams.toByteArray(existingMetadataFileInSubDir.get().get())) + assertThat(TestUtils.toByteArray(existingMetadataFileInSubDir.get())) .isEqualTo(new byte[] {0x02}); Optional nonExistingMetadataFile = @@ -159,7 +162,8 @@ public void bundleMetadataDirectoryNotAModule() throws Exception { .writeTo(bundleFile); AppBundle appBundle = AppBundle.buildFromZip(new ZipFile(bundleFile.toFile())); - assertThat(appBundle.getModules().keySet()).containsExactly(BundleModuleName.create("base")); + assertThat(appBundle.getFeatureModules().keySet()) + .containsExactly(BundleModuleName.create("base")); } @Test @@ -170,7 +174,8 @@ public void metaInfDirectoryNotAModule() throws Exception { .writeTo(bundleFile); AppBundle appBundle = AppBundle.buildFromZip(new ZipFile(bundleFile.toFile())); - assertThat(appBundle.getModules().keySet()).containsExactly(BundleModuleName.create("base")); + assertThat(appBundle.getFeatureModules().keySet()) + .containsExactly(BundleModuleName.create("base")); } @Test @@ -190,10 +195,7 @@ public void bundleModules_noDirectoryZipEntries() throws Exception { .build(); assertThat( - appBundle - .getBaseModule() - .getEntries() - .stream() + appBundle.getBaseModule().getEntries().stream() .filter(ModuleEntry::isDirectory) .collect(toImmutableList())) .isEmpty(); @@ -371,6 +373,46 @@ public void renderscript_bcFilesAbsent() throws Exception { assertThat(appBundle.has32BitRenderscriptCode()).isFalse(); } + @Test + public void baseAndAssetModule_fromModules_areSeparated() throws Exception { + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + "base", + baseModule -> + baseModule.setManifest(MANIFEST).addFile("dex/classes.dex", DUMMY_CONTENT)) + .addModule( + "some_asset_module", + module -> + module + .setManifest(REMOTE_ASSET_MANIFEST) + .addFile("assets/img1.png", DUMMY_CONTENT)) + .build(); + + assertThat(appBundle.getFeatureModules().keySet()) + .containsExactly(BundleModuleName.create("base")); + assertThat(appBundle.getAssetModules().keySet()) + .containsExactly(BundleModuleName.create("some_asset_module")); + } + + @Test + public void baseAndAssetModule_fromZipFile_areSeparated() throws Exception { + createBasicZipBuilder(BUNDLE_CONFIG) + .addFileWithProtoContent(ZipPath.create("base/manifest/AndroidManifest.xml"), MANIFEST) + .addFileWithContent(ZipPath.create("base/dex/classes.dex"), DUMMY_CONTENT) + .addFileWithContent(ZipPath.create("base/assets/file.txt"), DUMMY_CONTENT) + .addFileWithProtoContent( + ZipPath.create("remote_assets/manifest/AndroidManifest.xml"), REMOTE_ASSET_MANIFEST) + .addFileWithContent(ZipPath.create("remote_assets/assets/file.txt"), DUMMY_CONTENT) + .writeTo(bundleFile); + + AppBundle appBundle = AppBundle.buildFromZip(new ZipFile(bundleFile.toFile())); + assertThat(appBundle.getFeatureModules().keySet()) + .containsExactly(BundleModuleName.create("base")); + assertThat(appBundle.getAssetModules().keySet()) + .containsExactly(BundleModuleName.create("remote_assets")); + } + private static ZipBuilder createBasicZipBuilder(BundleConfig config) { ZipBuilder zipBuilder = new ZipBuilder(); zipBuilder.addFileWithContent(ZipPath.create("BundleConfig.pb"), config.toByteArray()); diff --git a/src/test/java/com/android/tools/build/bundletool/model/BundleModuleTest.java b/src/test/java/com/android/tools/build/bundletool/model/BundleModuleTest.java index e10c4493..5b19d854 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/BundleModuleTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/BundleModuleTest.java @@ -23,6 +23,8 @@ import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMinSdkCondition; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withOnDemandAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withOnDemandDelivery; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withSplitId; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTypeAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withUsesSplit; import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeModuleTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleFeatureTargeting; @@ -44,6 +46,7 @@ import com.android.bundle.Files.TargetedAssetsDirectory; import com.android.bundle.Files.TargetedNativeDirectory; import com.android.tools.build.bundletool.model.BundleModule.ModuleDeliveryType; +import com.android.tools.build.bundletool.model.BundleModule.ModuleType; import com.android.tools.build.bundletool.testing.BundleConfigBuilder; import java.io.IOException; import java.util.Arrays; @@ -418,6 +421,37 @@ public void getdeliveryType_installTimeElement_noConditions() throws Exception { assertThat(bundleModule.getDeliveryType()).isEqualTo(ModuleDeliveryType.ALWAYS_INITIAL_INSTALL); } + @Test + public void getModuleType_notSpecified_defaultsToFeature() { + BundleModule bundleModule = + createMinimalModuleBuilder() + .setAndroidManifestProto(androidManifest("com.test.app")) + .build(); + + assertThat(bundleModule.getModuleType()).isEqualTo(ModuleType.FEATURE_MODULE); + } + + @Test + public void getModuleType_feature() { + BundleModule bundleModule = + createMinimalModuleBuilder() + .setAndroidManifestProto(androidManifest("com.test.app", withTypeAttribute("feature"))) + .build(); + + assertThat(bundleModule.getModuleType()).isEqualTo(ModuleType.FEATURE_MODULE); + } + + @Test + public void getModuleType_remoteAsset() { + BundleModule bundleModule = + createMinimalModuleBuilder() + .setAndroidManifestProto( + androidManifest("com.test.app", withTypeAttribute("remote-asset"))) + .build(); + + assertThat(bundleModule.getModuleType()).isEqualTo(ModuleType.ASSET_MODULE); + } + @Test public void renderscriptFiles_present() throws Exception { BundleModule bundleModule = @@ -444,7 +478,7 @@ public void renderscriptFiles_absent() throws Exception { private static BundleModule.Builder createMinimalModuleBuilder() { return BundleModule.builder() .setName(BundleModuleName.create("testModule")) - .setAndroidManifestProto(androidManifest("com.test.app")) + .setAndroidManifestProto(androidManifest("com.test.app", withSplitId("testModule"))) .setBundleConfig(DEFAULT_BUNDLE_CONFIG); } } diff --git a/src/test/java/com/android/tools/build/bundletool/model/ClassDexNameSanitizerTest.java b/src/test/java/com/android/tools/build/bundletool/model/ClassDexNameSanitizerTest.java index 4ceaf3fe..4caec291 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ClassDexNameSanitizerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ClassDexNameSanitizerTest.java @@ -16,6 +16,7 @@ package com.android.tools.build.bundletool.model; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withSplitId; import static com.google.common.truth.Truth.assertThat; import com.android.bundle.Config.BundleConfig; @@ -78,6 +79,6 @@ private static BundleModule.Builder createBasicModule() throws IOException { .setBundleConfig(DEFAULT_BUNDLE_CONFIG) .addEntry(InMemoryModuleEntry.ofFile("assets/hello", new byte[] {0xD, 0xE, 0xA, 0xD})) .addEntry(InMemoryModuleEntry.ofFile("assets/world", new byte[] {0xB, 0xE, 0xE, 0xF})) - .setAndroidManifestProto(androidManifest("com.test.app")); + .setAndroidManifestProto(androidManifest("com.test.app", withSplitId("module"))); } } diff --git a/src/test/java/com/android/tools/build/bundletool/model/FileSystemModuleEntryTest.java b/src/test/java/com/android/tools/build/bundletool/model/FileSystemModuleEntryTest.java index 2bb18d35..79d3a4ab 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/FileSystemModuleEntryTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/FileSystemModuleEntryTest.java @@ -20,7 +20,7 @@ import static com.google.common.truth.Truth8.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.google.common.io.ByteStreams; +import com.android.tools.build.bundletool.testing.TestUtils; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; @@ -46,7 +46,7 @@ public void ofFile_existingFile_ok() throws Exception { assertThat(entry.getPath()).isEqualTo(ZipPath.create("assets/file.txt")); assertThat(entry.isDirectory()).isFalse(); - assertThat(ByteStreams.toByteArray(entry.getContent())).isEqualTo(fileData); + assertThat(TestUtils.getEntryContent(entry)).isEqualTo(fileData); } @Test @@ -98,8 +98,8 @@ public void setCompression_setFalse_ok() throws Exception { assertThat(entry.getPath()).isEqualTo(compressedEntry.getPath()); assertThat(entry.getFileSystemPath()).isEqualTo(compressedEntry.getFileSystemPath()); assertThat(entry.isDirectory()).isEqualTo(compressedEntry.isDirectory()); - assertThat(ByteStreams.toByteArray(entry.getContent())) - .isEqualTo(ByteStreams.toByteArray(compressedEntry.getContent())); + assertThat(TestUtils.getEntryContent(entry)) + .isEqualTo(TestUtils.getEntryContent(compressedEntry)); } @Test @@ -114,7 +114,7 @@ public void setCompression_setTrue_ok() throws Exception { assertThat(entry.getPath()).isEqualTo(compressedEntry.getPath()); assertThat(entry.getFileSystemPath()).isEqualTo(compressedEntry.getFileSystemPath()); assertThat(entry.isDirectory()).isEqualTo(compressedEntry.isDirectory()); - assertThat(ByteStreams.toByteArray(entry.getContent())) - .isEqualTo(ByteStreams.toByteArray(compressedEntry.getContent())); + assertThat(TestUtils.getEntryContent(entry)) + .isEqualTo(TestUtils.getEntryContent(compressedEntry)); } } diff --git a/src/test/java/com/android/tools/build/bundletool/model/GeneratedAssetSlicesTest.java b/src/test/java/com/android/tools/build/bundletool/model/GeneratedAssetSlicesTest.java new file mode 100755 index 00000000..0a6dfc5e --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/model/GeneratedAssetSlicesTest.java @@ -0,0 +1,75 @@ +/* + * 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; + +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTypeAttribute; +import static com.google.common.truth.Truth.assertThat; + +import com.android.bundle.Targeting.ApkTargeting; +import com.android.bundle.Targeting.VariantTargeting; +import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GeneratedAssetSlicesTest { + @Test + public void fromModuleSplits_empty() { + GeneratedAssetSlices generatedAssetSlices = + GeneratedAssetSlices.fromModuleSplits(ImmutableList.of()); + assertThat(generatedAssetSlices.size()).isEqualTo(0); + assertThat(generatedAssetSlices.getAssetSlices()).isEmpty(); + } + + @Test + public void fromModuleSplits_assetSlicesAndOtherSplits() { + ModuleSplit baseSplit = + ModuleSplit.builder() + .setAndroidManifest(AndroidManifest.create(androidManifest("com.test.app"))) + .setEntries(ImmutableList.of()) + .setMasterSplit(true) + .setSplitType(SplitType.SPLIT) + .setModuleName(BundleModuleName.create("base")) + .setApkTargeting(ApkTargeting.getDefaultInstance()) + .setVariantTargeting(VariantTargeting.getDefaultInstance()) + .build(); + ModuleSplit assetSlice = + ModuleSplit.builder() + .setAndroidManifest( + AndroidManifest.create( + androidManifest("com.test.app", withTypeAttribute("remote-asset")))) + .setEntries(ImmutableList.of()) + .setMasterSplit(true) + .setSplitType(SplitType.ASSET_SLICE) + .setModuleName(BundleModuleName.create("some_assets")) + .setApkTargeting(ApkTargeting.getDefaultInstance()) + .setVariantTargeting(VariantTargeting.getDefaultInstance()) + .build(); + + ImmutableList splits = ImmutableList.of(baseSplit, assetSlice); + GeneratedApks generatedApks = GeneratedApks.fromModuleSplits(splits); + assertThat(generatedApks.size()).isEqualTo(1); + assertThat(generatedApks.getSplitApks()).containsExactly(baseSplit); + + GeneratedAssetSlices generatedAssetSlices = GeneratedAssetSlices.fromModuleSplits(splits); + assertThat(generatedAssetSlices.size()).isEqualTo(1); + assertThat(generatedAssetSlices.getAssetSlices()).containsExactly(assetSlice); + } +} 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 c1c16035..c0392c4e 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 @@ -31,11 +31,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -409,6 +409,99 @@ public void deviceFeatureCondition_missingNamespace_throws() { "Missing required 'dist:name' attribute in the 'device-feature' condition element."); } + @Test + public void userCountriesCondition_parsesOk() { + XmlNode manifest = + createAndroidManifestWithConditions( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "user-countries") + .addChildElement(createCountryCodeEntry("pl")) + .addChildElement(createCountryCodeEntry("GB")) + .build()); + Optional deliveryElement = + ManifestDeliveryElement.fromManifestRootNode(manifest); + assertThat(deliveryElement).isPresent(); + + Optional userCountriesCondition = + deliveryElement.get().getModuleConditions().getUserCountriesCondition(); + assertThat(userCountriesCondition).isPresent(); + + assertThat(userCountriesCondition.get().getCountries()).containsExactly("PL", "GB"); + assertThat(userCountriesCondition.get().getExclude()).isFalse(); + } + + @Test + public void userCountriesCondition_parsesExclusionOk() { + XmlNode manifest = + createAndroidManifestWithConditions( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "user-countries") + .addAttribute( + XmlProtoAttributeBuilder.create(DISTRIBUTION_NAMESPACE_URI, "exclude") + .setValueAsBoolean(true)) + .addChildElement(createCountryCodeEntry("FR")) + .addChildElement(createCountryCodeEntry("SN")) + .build()); + Optional deliveryElement = + ManifestDeliveryElement.fromManifestRootNode(manifest); + assertThat(deliveryElement).isPresent(); + + Optional userCountriesCondition = + deliveryElement.get().getModuleConditions().getUserCountriesCondition(); + assertThat(userCountriesCondition).isPresent(); + + assertThat(userCountriesCondition.get().getCountries()).containsExactly("FR", "SN"); + assertThat(userCountriesCondition.get().getExclude()).isTrue(); + } + + @Test + public void userCountriesCondition_badCountryElementName_throws() { + XmlNode manifest = + createAndroidManifestWithConditions( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "user-countries") + .addChildElement( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "country-typo") + .addAttribute( + XmlProtoAttributeBuilder.create(DISTRIBUTION_NAMESPACE_URI, "code") + .setValueAsString("DE"))) + .build()); + Optional deliveryElement = + ManifestDeliveryElement.fromManifestRootNode(manifest); + assertThat(deliveryElement).isPresent(); + + ValidationException exception = + assertThrows( + ValidationException.class, + () -> deliveryElement.get().getModuleConditions().getUserCountriesCondition()); + + assertThat(exception) + .hasMessageThat() + .contains("Expected only elements inside "); + } + + @Test + public void userCountriesCondition_missingCodeAttribute_throws() { + XmlNode manifest = + createAndroidManifestWithConditions( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "user-countries") + .addChildElement( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "country") + .addAttribute( + XmlProtoAttributeBuilder.create(DISTRIBUTION_NAMESPACE_URI, "code-typo") + .setValueAsString("DE"))) + .build()); + Optional deliveryElement = + ManifestDeliveryElement.fromManifestRootNode(manifest); + assertThat(deliveryElement).isPresent(); + + ValidationException exception = + assertThrows( + ValidationException.class, + () -> deliveryElement.get().getModuleConditions().getUserCountriesCondition()); + + assertThat(exception) + .hasMessageThat() + .contains(" element is expected to have 'dist:code' attribute"); + } + private static XmlNode createAndroidManifestWithDeliveryElement( XmlProtoElementBuilder deliveryElement) { return XmlProtoNode.createElementNode( @@ -440,4 +533,11 @@ private static XmlNode createAndroidManifestWithConditions(XmlProtoElement... co .build()) .getProto(); } + + private static XmlProtoElementBuilder createCountryCodeEntry(String countryCode) { + return XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "country") + .addAttribute( + XmlProtoAttributeBuilder.create(DISTRIBUTION_NAMESPACE_URI, "code") + .setValueAsString(countryCode)); + } } diff --git a/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java b/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java index aa2b0a95..026778c5 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java @@ -25,6 +25,8 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.PROVIDER_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.SERVICE_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.SPLIT_NAME_ATTRIBUTE_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.SPLIT_NAME_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.TARGET_SANDBOX_VERSION_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.VALUE_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.VERSION_CODE_RESOURCE_ID; @@ -47,9 +49,10 @@ import com.android.aapt.Resources.XmlElement; import com.android.aapt.Resources.XmlNode; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttribute; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.protobuf.TextFormat; import org.junit.Test; @@ -574,6 +577,65 @@ public void removeSplitNameProvider() { ANDROID_NAMESPACE_URI, NAME_ATTRIBUTE_NAME, NAME_RESOURCE_ID, "FooProvider")); } + @Test + public void removeUnknownSplits() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest("com.test.app", withSplitNameProvider("FooProvider", "foo"))); + AndroidManifest editedManifest = + manifest.toEditor().removeUnknownSplitComponents(ImmutableSet.of("bar")).save(); + + ImmutableList providers = + editedManifest + .getManifestElement() + .getChildElement("application") + .getChildrenElements(PROVIDER_ELEMENT_NAME) + .map(XmlProtoElement::getProto) + .collect(toImmutableList()); + assertThat(providers).isEmpty(); + } + + @Test + public void removeUnknownSplits_keepsKnownSplits() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest("com.test.app", withSplitNameProvider("FooProvider", "foo"))); + AndroidManifest editedManifest = + manifest.toEditor().removeUnknownSplitComponents(ImmutableSet.of("foo")).save(); + + ImmutableList providers = + editedManifest + .getManifestElement() + .getChildElement("application") + .getChildrenElements(PROVIDER_ELEMENT_NAME) + .map(XmlProtoElement::getProto) + .collect(toImmutableList()); + assertThat(providers).hasSize(1); + XmlElement providerElement = Iterables.getOnlyElement(providers); + assertThat(providerElement.getAttributeList()) + .containsExactly( + xmlAttribute( + ANDROID_NAMESPACE_URI, NAME_ATTRIBUTE_NAME, NAME_RESOURCE_ID, "FooProvider"), + xmlAttribute( + ANDROID_NAMESPACE_URI, SPLIT_NAME_ATTRIBUTE_NAME, SPLIT_NAME_RESOURCE_ID, "foo")); + } + + @Test + public void removeUnknownSplits_keepsBaseSplits() { + AndroidManifest manifest = AndroidManifest.create(androidManifest("com.test.app")); + AndroidManifest editedManifest = + manifest.toEditor().removeUnknownSplitComponents(ImmutableSet.of()).save(); + + ImmutableList activities = + editedManifest + .getManifestElement() + .getChildElement("application") + .getChildrenElements(ACTIVITY_ELEMENT_NAME) + .map(XmlProtoElement::getProto) + .collect(toImmutableList()); + assertThat(activities).hasSize(1); + } + @Test public void addMetadataString() { AndroidManifest androidManifest = diff --git a/src/test/java/com/android/tools/build/bundletool/model/ModuleConditionsTest.java b/src/test/java/com/android/tools/build/bundletool/model/ModuleConditionsTest.java index 72ff4de2..142c79d8 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ModuleConditionsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ModuleConditionsTest.java @@ -17,11 +17,14 @@ package com.android.tools.build.bundletool.model; import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeModuleTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleExcludeCountriesTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleFeatureTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleIncludeCountriesTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.moduleMinSdkVersionTargeting; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import com.android.bundle.Targeting.ModuleTargeting; +import com.google.common.collect.ImmutableList; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,6 +78,36 @@ public void toTargeting_deviceFeatureVersionedConditions() { .isEqualTo(mergeModuleTargeting(moduleFeatureTargeting("com.feature1", 12))); } + @Test + public void toTargeting_userCountriesCondition() { + ModuleConditions moduleConditions = + ModuleConditions.builder() + .setUserCountriesCondition( + UserCountriesCondition.create(ImmutableList.of("PL", "US"), /* exclude= */ false)) + .build(); + + ModuleTargeting moduleTargeting = moduleConditions.toTargeting(); + + assertThat(moduleTargeting) + .ignoringRepeatedFieldOrder() + .isEqualTo(mergeModuleTargeting(moduleIncludeCountriesTargeting("PL", "US"))); + } + + @Test + public void toTargeting_exludeUserCountriesCondition() { + ModuleConditions moduleConditions = + ModuleConditions.builder() + .setUserCountriesCondition( + UserCountriesCondition.create(ImmutableList.of("PL", "US"), /* exclude= */ true)) + .build(); + + ModuleTargeting moduleTargeting = moduleConditions.toTargeting(); + + assertThat(moduleTargeting) + .ignoringRepeatedFieldOrder() + .isEqualTo(mergeModuleTargeting(moduleExcludeCountriesTargeting("PL", "US"))); + } + @Test public void toTargeting_mixedConditions() { ModuleConditions moduleConditions = @@ -82,6 +115,8 @@ public void toTargeting_mixedConditions() { .addDeviceFeatureCondition(DeviceFeatureCondition.create("com.feature1")) .addDeviceFeatureCondition(DeviceFeatureCondition.create("com.feature2")) .setMinSdkVersion(24) + .setUserCountriesCondition( + UserCountriesCondition.create(ImmutableList.of("FR"), /* exclude= */ false)) .build(); ModuleTargeting moduleTargeting = moduleConditions.toTargeting(); @@ -91,6 +126,7 @@ public void toTargeting_mixedConditions() { mergeModuleTargeting( moduleFeatureTargeting("com.feature1"), moduleFeatureTargeting("com.feature2"), - moduleMinSdkVersionTargeting(24))); + moduleMinSdkVersionTargeting(24), + moduleIncludeCountriesTargeting("FR"))); } } diff --git a/src/test/java/com/android/tools/build/bundletool/model/ModuleSplitTest.java b/src/test/java/com/android/tools/build/bundletool/model/ModuleSplitTest.java index 4f858a6e..0e6e8112 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ModuleSplitTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ModuleSplitTest.java @@ -56,9 +56,9 @@ import com.android.bundle.Targeting.ScreenDensityTargeting; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; import com.android.bundle.Targeting.VariantTargeting; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Arrays; @@ -410,6 +410,42 @@ public void splitNameRemoved() { xmlAttribute(ANDROID_NAMESPACE_URI, "name", NAME_RESOURCE_ID, "FooActivity")); } + @Test + public void removeUnknownSplits() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest("com.test.app", withSplitNameActivity("FooActivity", "foo"))); + ModuleSplit masterSplit = + ModuleSplit.builder() + .setModuleName(BundleModuleName.create("base")) + .setEntries( + fakeEntriesOf( + "res/drawable-ldpi/image.jpg", + "assets/labels.dat", + "res/drawable-hdpi/image.jpg", + "dex/classes.dex")) + .setApkTargeting(ApkTargeting.getDefaultInstance()) + .setVariantTargeting(lPlusVariantTargeting()) + .setMasterSplit(true) + .setAndroidManifest(manifest) + .build(); + masterSplit = masterSplit.removeUnknownSplitComponents(ImmutableSet.of()); + + ImmutableList activities = + masterSplit + .getAndroidManifest() + .getManifestElement() + .getChildElement("application") + .getChildrenElements(ACTIVITY_ELEMENT_NAME) + .map(XmlProtoElement::getProto) + .collect(toImmutableList()); + assertThat(activities).hasSize(1); + XmlElement activityElement = activities.get(0); + assertThat(activityElement.getAttributeList()) + .containsExactly( + xmlAttribute(ANDROID_NAMESPACE_URI, "name", NAME_RESOURCE_ID, "MainActivity")); + } + private ImmutableList fakeEntriesOf(String... entries) { return Arrays.stream(entries) .map(entry -> InMemoryModuleEntry.ofFile(entry, DUMMY_CONTENT)) diff --git a/src/test/java/com/android/tools/build/bundletool/model/ResourceIdTest.java b/src/test/java/com/android/tools/build/bundletool/model/ResourceIdTest.java index 791b1edc..f5eefed9 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ResourceIdTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ResourceIdTest.java @@ -46,6 +46,16 @@ public void getFullResourceId() { .isEqualTo(0x12345678); } + @Test + public void createFromFullResourceId() { + assertThat(ResourceId.create(0xffffffff)) + .isEqualTo( + ResourceId.builder().setPackageId(0xff).setTypeId(0xff).setEntryId(0xffff).build()); + assertThat(ResourceId.create(0x12345678)) + .isEqualTo( + ResourceId.builder().setPackageId(0x12).setTypeId(0x34).setEntryId(0x5678).build()); + } + @Test public void badPackageId_throws() { IllegalStateException exception = diff --git a/src/test/java/com/android/tools/build/bundletool/model/ResourceInjectorTest.java b/src/test/java/com/android/tools/build/bundletool/model/ResourceInjectorTest.java index 39ee552d..080870fe 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ResourceInjectorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ResourceInjectorTest.java @@ -30,7 +30,7 @@ import com.android.aapt.Resources.Type; import com.android.aapt.Resources.TypeId; import com.android.bundle.Targeting.ApkTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/src/test/java/com/android/tools/build/bundletool/model/SigningConfigurationTest.java b/src/test/java/com/android/tools/build/bundletool/model/SigningConfigurationTest.java index ecd72e52..eb354c96 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/SigningConfigurationTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/SigningConfigurationTest.java @@ -19,8 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.utils.flags.Flag.Password; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import java.io.FileOutputStream; import java.nio.file.Path; import java.security.KeyPair; @@ -64,8 +63,8 @@ public void extractFromKeystore_jks() throws Exception { SigningConfiguration.extractFromKeystore( keystorePath, KEY_ALIAS, - Optional.of(Password.createFromFlagValue("pass:" + KEYSTORE_PASSWORD)), - Optional.of(Password.createFromFlagValue("pass:" + KEY_PASSWORD))); + Optional.of(Password.createForTest(KEYSTORE_PASSWORD)), + Optional.of(Password.createForTest(KEY_PASSWORD))); assertThat(signingConfiguration.getPrivateKey()).isEqualTo(privateKey); assertThat(signingConfiguration.getCertificates()).containsExactly(certificate); @@ -79,8 +78,8 @@ public void extractFromKeystore_pkcs12() throws Exception { SigningConfiguration.extractFromKeystore( keystorePath, KEY_ALIAS, - Optional.of(Password.createFromFlagValue("pass:" + KEYSTORE_PASSWORD)), - Optional.of(Password.createFromFlagValue("pass:" + KEY_PASSWORD))); + Optional.of(Password.createForTest(KEYSTORE_PASSWORD)), + Optional.of(Password.createForTest(KEY_PASSWORD))); assertThat(signingConfiguration.getPrivateKey()).isEqualTo(privateKey); assertThat(signingConfiguration.getCertificates()).containsExactly(certificate); @@ -97,8 +96,8 @@ public void extractFromKeystore_wrongKeystorePassword() throws Exception { SigningConfiguration.extractFromKeystore( keystorePath, KEY_ALIAS, - Optional.of(Password.createFromFlagValue("pass:WrongPassword")), - Optional.of(Password.createFromFlagValue("pass:" + KEY_PASSWORD)))); + Optional.of(Password.createForTest("WrongPassword")), + Optional.of(Password.createForTest(KEY_PASSWORD)))); assertThat(e).hasMessageThat().contains("Incorrect keystore password"); } @@ -113,8 +112,8 @@ public void extractFromKeystore_wrongKeyPassword() throws Exception { SigningConfiguration.extractFromKeystore( keystorePath, KEY_ALIAS, - Optional.of(Password.createFromFlagValue("pass:" + KEYSTORE_PASSWORD)), - Optional.of(Password.createFromFlagValue("pass:WrongPassword")))); + Optional.of(Password.createForTest(KEYSTORE_PASSWORD)), + Optional.of(Password.createForTest("WrongPassword")))); assertThat(e).hasMessageThat().contains("Incorrect key password"); } @@ -131,7 +130,7 @@ public void extractFromKeystore_keyPasswordNotProvidedImpliesSameAsKeystorePassw SigningConfiguration.extractFromKeystore( keystorePath, KEY_ALIAS, - Optional.of(Password.createFromFlagValue("pass:" + KEYSTORE_PASSWORD)), + Optional.of(Password.createForTest(KEYSTORE_PASSWORD)), Optional.empty()); assertThat(signingConfiguration.getPrivateKey()).isEqualTo(privateKey); @@ -149,8 +148,8 @@ public void extractFromKeystore_keyAliasDoesNotExist() throws Exception { SigningConfiguration.extractFromKeystore( keystorePath, "BadKeyAlias", - Optional.of(Password.createFromFlagValue("pass:" + KEYSTORE_PASSWORD)), - Optional.of(Password.createFromFlagValue("pass:" + KEY_PASSWORD)))); + Optional.of(Password.createForTest(KEYSTORE_PASSWORD)), + Optional.of(Password.createForTest(KEY_PASSWORD)))); assertThat(e).hasMessageThat().contains("No key found with alias 'BadKeyAlias'"); } diff --git a/src/test/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilderTest.java b/src/test/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilderTest.java index a6e7e1e8..01cb1909 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilderTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/SplitsProtoXmlBuilderTest.java @@ -22,10 +22,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/src/test/java/com/android/tools/build/bundletool/model/WearApkLocatorTest.java b/src/test/java/com/android/tools/build/bundletool/model/WearApkLocatorTest.java index f0b1393d..39aa2166 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/WearApkLocatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/WearApkLocatorTest.java @@ -35,7 +35,7 @@ import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.TestData; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import com.android.tools.build.bundletool.testing.ResourcesUtils; import com.google.common.collect.ImmutableList; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/AlternativeVariantTargetingPopulatorTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/AlternativeVariantTargetingPopulatorTest.java similarity index 97% rename from src/test/java/com/android/tools/build/bundletool/targeting/AlternativeVariantTargetingPopulatorTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/AlternativeVariantTargetingPopulatorTest.java index 1838a667..9e45c72b 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/AlternativeVariantTargetingPopulatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/AlternativeVariantTargetingPopulatorTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeVariantTargeting; @@ -40,10 +40,10 @@ import com.android.tools.build.bundletool.model.GeneratedApks; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; -import com.android.tools.build.bundletool.targeting.AlternativeVariantTargetingPopulator.AbiAlternativesPopulator; -import com.android.tools.build.bundletool.targeting.AlternativeVariantTargetingPopulator.ScreenDensityAlternativesPopulator; -import com.android.tools.build.bundletool.targeting.AlternativeVariantTargetingPopulator.SdkVersionAlternativesPopulator; -import com.android.tools.build.bundletool.utils.Versions; +import com.android.tools.build.bundletool.model.targeting.AlternativeVariantTargetingPopulator.AbiAlternativesPopulator; +import com.android.tools.build.bundletool.model.targeting.AlternativeVariantTargetingPopulator.ScreenDensityAlternativesPopulator; +import com.android.tools.build.bundletool.model.targeting.AlternativeVariantTargetingPopulator.SdkVersionAlternativesPopulator; +import com.android.tools.build.bundletool.model.utils.Versions; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/ScreenDensitySelectorTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/ScreenDensitySelectorTest.java similarity index 94% rename from src/test/java/com/android/tools/build/bundletool/targeting/ScreenDensitySelectorTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/ScreenDensitySelectorTest.java index cdd99093..0a7ed8d5 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/ScreenDensitySelectorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/ScreenDensitySelectorTest.java @@ -14,8 +14,15 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; - +package com.android.tools.build.bundletool.model.targeting; + +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.ANY_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.HDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.TVDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XHDPI_VALUE; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.ANY_DPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.HDPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.LDPI; @@ -26,13 +33,6 @@ import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.XXXHDPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.forDpi; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.onlyConfig; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.ANY_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.HDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.MDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.TVDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.XHDPI_VALUE; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/TargetedDirectorySegmentTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectorySegmentTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/targeting/TargetedDirectorySegmentTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectorySegmentTest.java index 085906d8..9cf0dfc5 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/TargetedDirectorySegmentTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectorySegmentTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.graphicsApiTargeting; @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/TargetedDirectoryTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectoryTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/targeting/TargetedDirectoryTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectoryTest.java index 0daeb386..51f4bb38 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/TargetedDirectoryTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetedDirectoryTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.graphicsApiTargeting; @@ -26,8 +26,8 @@ import com.android.bundle.Targeting.AssetsDirectoryTargeting; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/TargetingComparatorsTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingComparatorsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/targeting/TargetingComparatorsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingComparatorsTest.java index 9a4215cd..955516fa 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/TargetingComparatorsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingComparatorsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.android.bundle.Targeting.Abi.AbiAlias.ARM64_V8A; import static com.android.bundle.Targeting.Abi.AbiAlias.ARMEABI; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/TargetingGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingGeneratorTest.java similarity index 99% rename from src/test/java/com/android/tools/build/bundletool/targeting/TargetingGeneratorTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingGeneratorTest.java index 7b6cfe39..514e2d11 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/TargetingGeneratorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingGeneratorTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.alternativeLanguageTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; @@ -39,9 +39,9 @@ import com.android.bundle.Files.TargetedNativeDirectory; import com.android.bundle.Targeting.AssetsDirectoryTargeting; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AbiName; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; diff --git a/src/test/java/com/android/tools/build/bundletool/targeting/TargetingUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingUtilsTest.java similarity index 96% rename from src/test/java/com/android/tools/build/bundletool/targeting/TargetingUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingUtilsTest.java index a559d987..fc1e170b 100755 --- a/src/test/java/com/android/tools/build/bundletool/targeting/TargetingUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/targeting/TargetingUtilsTest.java @@ -14,10 +14,10 @@ * limitations under the License */ -package com.android.tools.build.bundletool.targeting; +package com.android.tools.build.bundletool.model.targeting; -import static com.android.tools.build.bundletool.targeting.TargetingUtils.getMaxSdk; -import static com.android.tools.build.bundletool.targeting.TargetingUtils.getMinSdk; +import static com.android.tools.build.bundletool.model.targeting.TargetingUtils.getMaxSdk; +import static com.android.tools.build.bundletool.model.targeting.TargetingUtils.getMinSdk; import static com.android.tools.build.bundletool.testing.TargetingUtils.abiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.graphicsApiTargeting; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/ApkSizeUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/ApkSizeUtilsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/ApkSizeUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/ApkSizeUtilsTest.java index adae0f56..60a87622 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/ApkSizeUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/ApkSizeUtilsTest.java @@ -14,9 +14,10 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.android.bundle.Targeting.Abi.AbiAlias.X86; +import static com.android.tools.build.bundletool.model.utils.ApkSizeUtils.getCompressedSizeByApkPaths; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApkDescription; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApksArchiveFile; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createMasterApkDescription; @@ -26,7 +27,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.apkAbiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.sdkVersionFrom; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; -import static com.android.tools.build.bundletool.utils.ApkSizeUtils.getCompressedSizeByApkPaths; import static com.google.common.truth.Truth.assertThat; import com.android.bundle.Commands.BuildApksResult; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/CollectorUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/CollectorUtilsTest.java similarity index 94% rename from src/test/java/com/android/tools/build/bundletool/utils/CollectorUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/CollectorUtilsTest.java index 2dde8e39..7e1e4353 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/CollectorUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/CollectorUtilsTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.utils.CollectorUtils.combineMaps; +import static com.android.tools.build.bundletool.model.utils.CollectorUtils.combineMaps; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Futures.immediateFuture; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/CsvFormatterTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/CsvFormatterTest.java similarity index 96% rename from src/test/java/com/android/tools/build/bundletool/utils/CsvFormatterTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/CsvFormatterTest.java index 0ddd7256..8207569e 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/CsvFormatterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/CsvFormatterTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.utils.CsvFormatter.CRLF; +import static com.android.tools.build.bundletool.model.utils.CsvFormatter.CRLF; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/EnumMapperTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/EnumMapperTest.java similarity index 97% rename from src/test/java/com/android/tools/build/bundletool/utils/EnumMapperTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/EnumMapperTest.java index 36b29f80..59a77bbd 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/EnumMapperTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/EnumMapperTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/GetSizeCsvUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/GetSizeCsvUtilsTest.java similarity index 86% rename from src/test/java/com/android/tools/build/bundletool/utils/GetSizeCsvUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/GetSizeCsvUtilsTest.java index 846eb994..8ddb0202 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/GetSizeCsvUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/GetSizeCsvUtilsTest.java @@ -14,14 +14,14 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.ABI; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.LANGUAGE; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.SCREEN_DENSITY; -import static com.android.tools.build.bundletool.commands.GetSizeCommand.Dimension.SDK; -import static com.android.tools.build.bundletool.utils.CsvFormatter.CRLF; -import static com.android.tools.build.bundletool.utils.GetSizeCsvUtils.getSizeTotalOutputInCsv; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.ABI; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.LANGUAGE; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.SCREEN_DENSITY; +import static com.android.tools.build.bundletool.model.GetSizeRequest.Dimension.SDK; +import static com.android.tools.build.bundletool.model.utils.CsvFormatter.CRLF; +import static com.android.tools.build.bundletool.model.utils.GetSizeCsvUtils.getSizeTotalOutputInCsv; import static com.google.common.truth.Truth.assertThat; import com.android.tools.build.bundletool.model.ConfigurationSizes; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/ResourcesUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/ResourcesUtilsTest.java similarity index 87% rename from src/test/java/com/android/tools/build/bundletool/utils/ResourcesUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/ResourcesUtilsTest.java index 392c3d5a..ee9864db 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/ResourcesUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/ResourcesUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.MDPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.entry; @@ -30,12 +30,12 @@ import com.android.aapt.ConfigurationOuterClass.Configuration; import com.android.aapt.Resources.Entry; import com.android.aapt.Resources.ResourceTable; +import com.android.tools.build.bundletool.model.ResourceTableEntry; import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.Optional; -import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -60,7 +60,8 @@ public void filter_noFiltering_returnsIdentity() throws Exception { "res/drawable/image.png", Configuration.getDefaultInstance()))))); ResourceTable filteredTable = - ResourcesUtils.filterResourceTable(table, Predicates.alwaysFalse(), Function.identity()); + ResourcesUtils.filterResourceTable( + table, Predicates.alwaysFalse(), ResourceTableEntry::getEntry); assertThat(filteredTable).ignoringRepeatedFieldOrder().isEqualTo(table); } @@ -86,7 +87,8 @@ public void filter_withConfigValuesFilterFn() throws Exception { ResourcesUtils.filterResourceTable( table, Predicates.alwaysFalse(), - entry -> entry.toBuilder().removeConfigValue(1).build()); + resourceTableEntry -> + resourceTableEntry.getEntry().toBuilder().removeConfigValue(1).build()); assertThat(filteredTable) .ignoringRepeatedFieldOrder() @@ -131,7 +133,9 @@ public void filter_withRemoveTypePredicate() throws Exception { ResourceTable filteredTable = ResourcesUtils.filterResourceTable( - table, type -> type.getTypeId().getId() == 0x02, Function.identity()); + table, + resource -> resource.getType().getTypeId().getId() == 0x02, + ResourceTableEntry::getEntry); assertThat(filteredTable) .ignoringRepeatedFieldOrder() @@ -150,6 +154,34 @@ public void filter_withRemoveTypePredicate() throws Exception { "res/drawable/image.png", Configuration.getDefaultInstance())))))); } + @Test + public void filter_withEntryPredicate() { + ResourceTable resourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResource("image1", "drawable/image1.jpg") + .addDrawableResource("image2", "drawable/image2.jpg") + .addStringResource("label_hello", "Hello world") + .build(); + + ResourceTable filteredTable = + ResourcesUtils.filterResourceTable( + resourceTable, + /* removeEntryPredicate= */ resourceTableEntry -> + resourceTableEntry.getResourceId().getFullResourceId() + == 0x7f010001 /* image2.jpg */, + ResourceTableEntry::getEntry); + + assertThat(filteredTable) + .ignoringRepeatedFieldOrder() + .isEqualTo( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResource("image1", "drawable/image1.jpg") + .addStringResource("label_hello", "Hello world") + .build()); + } + @Test public void filter_removesEmptyPackagesTypesAndEntries() throws Exception { ResourceTable table = @@ -281,13 +313,14 @@ public void lookupEntryByResourceId_negativeId() { assertThat(extractStringValues(worldResource.get())).containsExactly("res/string/world.xml"); } - private static Entry removeDefaultResources(Entry entry) { + private static Entry removeDefaultResources(ResourceTableEntry entry) { return entry + .getEntry() .toBuilder() .clearConfigValue() .addAllConfigValue( Iterables.filter( - entry.getConfigValueList(), + entry.getEntry().getConfigValueList(), configValue -> !configValue.getConfig().equals(Configuration.getDefaultInstance()))) .build(); } diff --git a/src/test/java/com/android/tools/build/bundletool/utils/ResultUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/ResultUtilsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/ResultUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/ResultUtilsTest.java index 2cce29f2..0592c9fd 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/ResultUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/ResultUtilsTest.java @@ -14,8 +14,9 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; +import static com.android.tools.build.bundletool.model.utils.ResultUtils.readTableOfContents; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApkDescription; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApksArchiveFile; import static com.android.tools.build.bundletool.testing.ApksArchiveHelpers.createApksDirectory; @@ -29,7 +30,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.lPlusVariantTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.sdkVersionFrom; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; -import static com.android.tools.build.bundletool.utils.ResultUtils.readTableOfContents; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/SdkToolsLocatorTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/SdkToolsLocatorTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/SdkToolsLocatorTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/SdkToolsLocatorTest.java index 5c97dc65..bb973f18 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/SdkToolsLocatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/SdkToolsLocatorTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/SplitsXmlInjectorTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/SplitsXmlInjectorTest.java similarity index 96% rename from src/test/java/com/android/tools/build/bundletool/utils/SplitsXmlInjectorTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/SplitsXmlInjectorTest.java index e5616856..2ed45b42 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/SplitsXmlInjectorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/SplitsXmlInjectorTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.android.tools.build.bundletool.model.BundleModuleName.BASE_MODULE_NAME; import static com.android.tools.build.bundletool.model.ModuleSplit.SplitType.INSTANT; @@ -42,6 +42,7 @@ import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.SplitsProtoXmlBuilder; import com.android.tools.build.bundletool.testing.ResourceTableBuilder; +import com.android.tools.build.bundletool.testing.TestUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -163,7 +164,9 @@ public void process() throws Exception { .addLanguageMapping(BundleModuleName.create(BASE_MODULE_NAME), "ru", "config.ru") .addLanguageMapping(BundleModuleName.create(BASE_MODULE_NAME), "fr", "config.fr") .build(); - assertThat(XmlNode.parseFrom(processedBaseMasterSplit.getEntries().get(0).getContent())) + assertThat( + XmlNode.parseFrom( + TestUtils.getEntryContent(processedBaseMasterSplit.getEntries().get(0)))) .ignoringRepeatedFieldOrder() .isEqualTo(expectedSplitsProtoXml); } @@ -210,7 +213,8 @@ public void process_standaloneSplitTypes( .addLanguageMapping(BundleModuleName.create(BASE_MODULE_NAME), "ru", "") .addLanguageMapping(BundleModuleName.create(BASE_MODULE_NAME), "fr", "") .build(); - assertThat(XmlNode.parseFrom(processedStandalone.getEntries().get(0).getContent())) + assertThat( + XmlNode.parseFrom(TestUtils.getEntryContent(processedStandalone.getEntries().get(0)))) .ignoringRepeatedFieldOrder() .isEqualTo(expectedSplitsProtoXml); } diff --git a/src/test/java/com/android/tools/build/bundletool/utils/TargetingProtoUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/TargetingProtoUtilsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/TargetingProtoUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/TargetingProtoUtilsTest.java index 1a4c23b4..13282cd7 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/TargetingProtoUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/TargetingProtoUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.android.tools.build.bundletool.testing.TargetingUtils.abiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.alternativeLanguageTargeting; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/ThrowableUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/ThrowableUtilsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/ThrowableUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/ThrowableUtilsTest.java index 6a41fa39..f9fcdb3a 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/ThrowableUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/ThrowableUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/ZipUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/ZipUtilsTest.java similarity index 97% rename from src/test/java/com/android/tools/build/bundletool/utils/ZipUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/ZipUtilsTest.java index 0e765287..806b5a41 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/ZipUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/ZipUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils; +package com.android.tools.build.bundletool.model.utils; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/files/FilePreconditionsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/files/FilePreconditionsTest.java similarity index 80% rename from src/test/java/com/android/tools/build/bundletool/utils/files/FilePreconditionsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/files/FilePreconditionsTest.java index 39f19a24..bebf4a17 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/files/FilePreconditionsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/files/FilePreconditionsTest.java @@ -14,24 +14,21 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.files; - -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkDirectoryExists; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkDirectoryExistsAndEmpty; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileDoesNotExist; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileHasExtension; -import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileNamesAreUnique; +package com.android.tools.build.bundletool.model.utils.files; + +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkDirectoryExists; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkDirectoryExistsAndEmpty; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileDoesNotExist; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; +import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileHasExtension; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.OsPlatform; +import com.android.tools.build.bundletool.model.utils.OsPlatform; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -123,27 +120,6 @@ public void checkFileHasExtension_failure() throws Exception { .matches("Module 'AndroidManifest.xml' is expected to have '.zip' extension."); } - @Test - public void checkFileNamesAreUnique_success() { - ZipPath fileA = ZipPath.create("a/some-entry"); - ZipPath fileB = ZipPath.create("a/other-entry"); - - checkFileNamesAreUnique("Entries", Arrays.asList(fileA, fileB)); - } - - @Test - public void checkFileNamesAreUnique_failure() { - ZipPath fileInA = ZipPath.create("a/same-leaf"); - ZipPath fileInB = ZipPath.create("b/same-leaf"); - - IllegalArgumentException exception = - assertThrows( - IllegalArgumentException.class, - () -> checkFileNamesAreUnique("Entries", Arrays.asList(fileInA, fileInB))); - - assertThat(exception).hasMessageThat().contains("must have unique filenames"); - } - @Test public void checkDirectoryExists_nonExistent_fail() { Path nonExistent = Paths.get("non-existent"); diff --git a/src/test/java/com/android/tools/build/bundletool/utils/files/FileUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/files/FileUtilsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/files/FileUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/files/FileUtilsTest.java index be4715b0..d88dc58f 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/files/FileUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/files/FileUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tools.build.bundletool.utils.files; +package com.android.tools.build.bundletool.model.utils.files; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeBuilderTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeBuilderTest.java similarity index 99% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeBuilderTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeBuilderTest.java index 221d2a17..6c284e30 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeBuilderTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeBuilderTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.google.common.base.Preconditions.checkState; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeTest.java similarity index 99% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeTest.java index 420810f2..e1cefb04 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoAttributeTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoAttributeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.google.common.truth.Truth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementBuilderTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementBuilderTest.java similarity index 95% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementBuilderTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementBuilderTest.java index ca5c4a12..ab72ea66 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementBuilderTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementBuilderTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -528,4 +528,25 @@ public void getNamespaceDeclarations() { XmlProtoNamespace.create("ns", "http://namespace"), XmlProtoNamespace.create("ns2", "http://namespace2")); } + + @Test + public void removeChildrenElementsIf() { + XmlElement childElement = XmlElement.newBuilder().setName("hello").build(); + XmlElement matchingElement = XmlElement.newBuilder().setName("foo").build(); + XmlElement protoElement = + XmlElement.newBuilder() + .addChild(XmlNode.newBuilder().setElement(childElement)) + .addChild(XmlNode.newBuilder().setElement(matchingElement)) + .build(); + + XmlProtoElementBuilder element = new XmlProtoElementBuilder(protoElement.toBuilder()); + element.removeChildrenElementsIf( + node -> node.isElement() && node.getElement().getName().equals("foo")); + + ImmutableList fetchedElements = + element.getChildrenElements().collect(toImmutableList()); + assertThat(fetchedElements).hasSize(1); + XmlProtoElementBuilder fetchedElement = fetchedElements.get(0); + assertThat(fetchedElement.getProto().build()).isEqualTo(childElement); + } } diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementTest.java similarity index 99% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementTest.java index 9581dce2..cd87b47b 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoElementTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoElementTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeBuilderTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeBuilderTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeBuilderTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeBuilderTest.java index 13897494..0a6525e1 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeBuilderTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeBuilderTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeTest.java index 748cbaf2..5205050f 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoNodeTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoNodeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoPrintUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoPrintUtilsTest.java similarity index 98% rename from src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoPrintUtilsTest.java rename to src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoPrintUtilsTest.java index 9d3f38c1..a900d973 100755 --- a/src/test/java/com/android/tools/build/bundletool/utils/xmlproto/XmlProtoPrintUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/utils/xmlproto/XmlProtoPrintUtilsTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.utils.xmlproto; +package com.android.tools.build.bundletool.model.utils.xmlproto; import static com.google.common.truth.Truth.assertThat; @@ -229,7 +229,7 @@ public void getCompoundValueAsString_array() { .build(); assertThat(XmlProtoPrintUtils.getCompoundValueAsString(compoundValue)) - .isEqualTo("[name1, name2]"); + .isEqualTo("[\"name1\", \"name2\"]"); } @Test @@ -245,7 +245,7 @@ public void getCompoundValueAsString_plural() { .build(); assertThat(XmlProtoPrintUtils.getCompoundValueAsString(compoundValue)) - .isEqualTo("{ONE=one, MANY=many}"); + .isEqualTo("{ONE=\"one\", MANY=\"many\"}"); } @Test @@ -259,7 +259,7 @@ public void getCompoundValueAsString_style() { .build(); assertThat(XmlProtoPrintUtils.getCompoundValueAsString(compoundValue)) - .isEqualTo("[name1, name2]"); + .isEqualTo("[\"name1\", \"name2\"]"); } @Test diff --git a/src/test/java/com/android/tools/build/bundletool/version/VersionTest.java b/src/test/java/com/android/tools/build/bundletool/model/version/VersionTest.java similarity index 95% rename from src/test/java/com/android/tools/build/bundletool/version/VersionTest.java rename to src/test/java/com/android/tools/build/bundletool/model/version/VersionTest.java index 0ca393fb..6500c3bb 100755 --- a/src/test/java/com/android/tools/build/bundletool/version/VersionTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/version/VersionTest.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.tools.build.bundletool.version; +package com.android.tools.build.bundletool.model.version; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import org.junit.Test; 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 40a99cf4..85ebbcae 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 @@ -20,7 +20,7 @@ import static com.android.tools.build.bundletool.model.OptimizationDimension.SCREEN_DENSITY; import static com.google.common.truth.Truth.assertThat; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/optimizations/OptimizationsMergerTest.java b/src/test/java/com/android/tools/build/bundletool/optimizations/OptimizationsMergerTest.java index 03783ca8..1547339d 100755 --- a/src/test/java/com/android/tools/build/bundletool/optimizations/OptimizationsMergerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/optimizations/OptimizationsMergerTest.java @@ -23,8 +23,8 @@ import com.android.bundle.Config.SplitDimension; import com.android.tools.build.bundletool.model.OptimizationDimension; +import com.android.tools.build.bundletool.model.version.Version; import com.android.tools.build.bundletool.testing.BundleConfigBuilder; -import com.android.tools.build.bundletool.version.Version; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.junit.Before; diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitterTest.java index ff802259..49205277 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/AbiNativeLibrariesSplitterTest.java @@ -37,9 +37,9 @@ import com.android.bundle.Files.NativeLibraries; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleSplit; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/AssetSlicesGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/AssetSlicesGeneratorTest.java new file mode 100755 index 00000000..fe939394 --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/splitters/AssetSlicesGeneratorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 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.splitters; + +import static com.android.tools.build.bundletool.model.OptimizationDimension.LANGUAGE; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTypeAttribute; +import static com.android.tools.build.bundletool.testing.TargetingUtils.apkLanguageTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.assets; +import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.languageTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedAssetsDirectory; +import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; + +import com.android.bundle.Targeting.ApkTargeting; +import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.ModuleSplit; +import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; +import com.android.tools.build.bundletool.testing.BundleModuleBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AssetSlicesGeneratorTest { + + @Test + public void singleAssetModule() throws Exception { + ImmutableList modules = + ImmutableList.of( + new BundleModuleBuilder("asset_module") + .addFile("assets/some_asset.txt") + .setManifest(androidManifest("com.test.app", withTypeAttribute("remote-asset"))) + .build()); + + ImmutableList assetSlices = + new AssetSlicesGenerator(modules, ApkGenerationConfiguration.getDefaultInstance()) + .generateAssetSlices(); + + assertThat(assetSlices).hasSize(1); + + ModuleSplit assetSlice = assetSlices.get(0); + + assertThat(assetSlice.getModuleName().getName()).isEqualTo("asset_module"); + assertThat(assetSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(assetSlice.isMasterSplit()).isTrue(); + assertThat(assetSlice.getApkTargeting()).isEqualToDefaultInstance(); + assertThat(extractPaths(assetSlice.getEntries())).containsExactly("assets/some_asset.txt"); + } + + @Test + public void singleAssetModule_LanguageTargeting() throws Exception { + ImmutableList modules = + ImmutableList.of( + new BundleModuleBuilder("asset_module") + .addFile("assets/images#lang_en/image.jpg") + .addFile("assets/images#lang_es/image.jpg") + .setAssetsConfig( + assets( + targetedAssetsDirectory( + "assets/images#lang_es", + assetsDirectoryTargeting(languageTargeting("es"))), + targetedAssetsDirectory( + "assets/images#lang_en", + assetsDirectoryTargeting(languageTargeting("en"))))) + .setManifest(androidManifest("com.test.app", withTypeAttribute("remote-asset"))) + .build()); + + ImmutableList assetSlices = + new AssetSlicesGenerator( + modules, + ApkGenerationConfiguration.builder() + .setOptimizationDimensions(ImmutableSet.of(LANGUAGE)) + .build()) + .generateAssetSlices(); + + assertThat(assetSlices).hasSize(2); + + Map slicesByTargeting = + Maps.uniqueIndex(assetSlices, ModuleSplit::getApkTargeting); + + assertThat(slicesByTargeting.keySet()) + .containsExactly(apkLanguageTargeting("es"), apkLanguageTargeting("en")); + ModuleSplit enSlice = slicesByTargeting.get(apkLanguageTargeting("en")); + assertThat(enSlice.getModuleName().getName()).isEqualTo("asset_module"); + assertThat(enSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(extractPaths(enSlice.getEntries())) + .containsExactly("assets/images#lang_en/image.jpg"); + + ModuleSplit esSlice = slicesByTargeting.get(apkLanguageTargeting("es")); + assertThat(esSlice.getModuleName().getName()).isEqualTo("asset_module"); + assertThat(esSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(extractPaths(esSlice.getEntries())) + .containsExactly("assets/images#lang_es/image.jpg"); + } +} 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 932ac599..c66fda80 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 @@ -23,19 +23,28 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.ACTIVITY_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.ANDROID_NAMESPACE_URI; import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_RESOURCE_ID; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.HDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.LDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MDPI_VALUE; +import static com.android.tools.build.bundletool.testing.DeviceFactory.abis; +import static com.android.tools.build.bundletool.testing.DeviceFactory.density; +import static com.android.tools.build.bundletool.testing.DeviceFactory.locales; +import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; +import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withSplitNameActivity; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.xmlAttribute; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.HDPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.LDPI; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.MDPI; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.USER_PACKAGE_OFFSET; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.createResourceTable; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.entry; import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.fileReference; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.locale; 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.android.tools.build.bundletool.testing.TargetingUtils.abiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.apexImageTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.apexImages; import static com.android.tools.build.bundletool.testing.TargetingUtils.apkAbiTargeting; @@ -64,6 +73,7 @@ import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlElement; import com.android.aapt.Resources.XmlNode; +import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Files.ApexImages; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; @@ -75,9 +85,10 @@ 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; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -87,6 +98,7 @@ import com.google.protobuf.Message; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -167,10 +179,16 @@ public void shardByNoDimension_producesOneApk( targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), targetedNativeDirectory("lib/x86_64", nativeDirectoryTargeting(X86_64)))) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .build(); BundleSharder bundleSharder = @@ -231,8 +249,24 @@ public void removeSplitNameFromShard() throws Exception { xmlAttribute(ANDROID_NAMESPACE_URI, "name", NAME_RESOURCE_ID, "FooActivity")); } + @DataPoints("deviceSpecs") + public static final ImmutableSet> DEVICE_SPECS = + ImmutableSet.of( + Optional.empty(), + Optional.of( + mergeSpecs( + sdkVersion(28), abis("x86_64"), density(DensityAlias.MDPI), locales("en-US")))); + @Test - public void shardByAbi_havingNoNativeTargeting_producesOneApk() throws Exception { + @Theory + public void shardByAbi_havingNoNativeTargeting_producesOneApk( + @FromDataPoints("deviceSpecs") Optional deviceSpec) throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + deviceSpec); BundleModule bundleModule = new BundleModuleBuilder("base") .addFile("assets/file.txt") @@ -244,10 +278,16 @@ public void shardByAbi_havingNoNativeTargeting_producesOneApk() throws Exception .addFile("root/license.dat") .setManifest(androidManifest("com.test.app")) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .build(); ImmutableList shards = @@ -273,7 +313,15 @@ public void shardByAbi_havingNoNativeTargeting_producesOneApk() throws Exception } @Test - public void shardByAbi_havingSingleAbi_producesOneApk() throws Exception { + @Theory + public void shardByAbi_havingSingleAbi_producesOneApk( + @FromDataPoints("deviceSpecs") Optional deviceSpec) throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + deviceSpec); BundleModule bundleModule = new BundleModuleBuilder("base") .addFile("assets/file.txt") @@ -287,10 +335,16 @@ public void shardByAbi_havingSingleAbi_producesOneApk() throws Exception { nativeLibraries( targetedNativeDirectory("lib/x86_64", nativeDirectoryTargeting(X86_64)))) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .build(); ImmutableList shards = @@ -333,10 +387,16 @@ public void shardByAbi_havingManyAbis_producesManyApks() throws Exception { targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), targetedNativeDirectory("lib/x86_64", nativeDirectoryTargeting(X86_64)))) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .build(); ImmutableList shards = @@ -385,6 +445,70 @@ public void shardByAbi_havingManyAbis_producesManyApks() throws Exception { .containsNoneOf("lib/armeabi/libtest.so", "lib/x86/libtest.so"); } + @Test + public void shardByAbi_havingManyAbisWithDeviceSpec_producesSingleApk() throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + Optional.of( + mergeSpecs( + sdkVersion(28), + abis("armeabi"), + density(DensityAlias.MDPI), + locales("en-US")))); + BundleModule bundleModule = + new BundleModuleBuilder("base") + .addFile("assets/file.txt") + .addFile("dex/classes.dex") + .addFile("lib/armeabi/libtest.so") + .addFile("lib/x86/libtest.so") + .addFile("lib/x86_64/libtest.so") + .addFile("res/drawable/image.jpg") + .addFile("res/drawable-mdpi/image.jpg") + .addFile("root/license.dat") + .setManifest(androidManifest("com.test.app")) + .setNativeConfig( + nativeLibraries( + targetedNativeDirectory("lib/armeabi", nativeDirectoryTargeting(ARMEABI)), + targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), + targetedNativeDirectory("lib/x86_64", nativeDirectoryTargeting(X86_64)))) + .setResourceTable( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) + .build(); + + ImmutableList shards = + bundleSharder.shardBundle( + ImmutableList.of(bundleModule), + ImmutableSet.of(OptimizationDimension.ABI), + DEFAULT_METADATA); + + assertThat(shards).hasSize(1); + ModuleSplit armeabiShard = shards.get(0); + assertThat(armeabiShard.getApkTargeting()) + .isEqualTo(apkAbiTargeting(ARMEABI, ImmutableSet.of(X86, X86_64))); + assertThat(armeabiShard.getVariantTargeting()).isEqualToDefaultInstance(); + assertThat(armeabiShard.getSplitType()).isEqualTo(SplitType.STANDALONE); + assertThat(extractPaths(armeabiShard.getEntries())) + .containsExactly( + "assets/file.txt", + "dex/classes.dex", + "lib/armeabi/libtest.so", + "res/drawable/image.jpg", + "res/drawable-mdpi/image.jpg", + "root/license.dat"); + } + @Test public void shardByAbi_64BitAbisDisabled_filteredOut() throws Exception { BundleModule bundleModule = @@ -573,11 +697,18 @@ public void shardByDensity_havingNoResources_producesOneApk() throws Exception { @Test public void shardByDensity_havingNonDensityResources_producesOneApk() throws Exception { ResourceTable resourceTable = - createResourceTable( - "image", - fileReference("res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference( - "res/drawable-de/image.jpg", Configuration.newBuilder().setLocale("de").build())); + new ResourceTableBuilder() + .addPackage("com.test.app") + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + Configuration.getDefaultInstance(), + "res/drawable/image.jpg", + locale("de"), + "res/drawable-de/image.jpg")) + .build(); + BundleModule bundleModule = new BundleModuleBuilder("base") .addFile("assets/file.txt") @@ -632,7 +763,11 @@ public void shardByDensity_havingOnlyOneDensityResource_producesSingleApk() thro .addFile("root/license.dat") .setManifest(androidManifest("com.test.app")) .setResourceTable( - createResourceTable("image", fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", ImmutableMap.of(HDPI_VALUE, "res/drawable-hdpi/image.jpg")) + .build()) .build(); ImmutableList shards = @@ -661,7 +796,15 @@ public void shardByDensity_havingOnlyOneDensityResource_producesSingleApk() thro public void shardByDensity_assetsDensityTargetingIsIgnored() throws Exception {} @Test - public void shardByAbiAndDensity_havingNoAbiAndNoResources_producesOneApk() throws Exception { + @Theory + public void shardByAbiAndDensity_havingNoAbiAndNoResources_producesOneApk( + @FromDataPoints("deviceSpecs") Optional deviceSpec) throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + deviceSpec); BundleModule bundleModule = new BundleModuleBuilder("base") .addFile("assets/file.txt") @@ -700,10 +843,16 @@ public void shardByAbiAndDensity_havingOneAbiAndSomeDensityResource_producesMany .setNativeConfig( nativeLibraries(targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)))) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + HDPI_VALUE, + "res/drawable-hdpi/image.jpg")) + .build()) .build(); ImmutableList shards = @@ -755,6 +904,74 @@ public void shardByAbiAndDensity_havingOneAbiAndSomeDensityResource_producesMany mergeApkTargeting(apkAbiTargeting(X86), TVDPI_TARGETING)); } + @Test + public void + shardByAbiAndDensity_havingOneAbiAndSomeDensityResourceWithDeviceSpec_producesSingleApk() + throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + Optional.of( + mergeSpecs( + sdkVersion(28), abis("x86"), density(DensityAlias.MDPI), locales("en-US")))); + BundleModule bundleModule = + new BundleModuleBuilder("base") + .addFile("assets/file.txt") + .addFile("dex/classes.dex") + .addFile("lib/x86/libtest.so") + .addFile("res/drawable-ldpi/image.jpg") + .addFile("res/drawable-hdpi/image.jpg") + .addFile("root/license.dat") + .setManifest(androidManifest("com.test.app")) + .setNativeConfig( + nativeLibraries(targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)))) + .setResourceTable( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + HDPI_VALUE, + "res/drawable-hdpi/image.jpg")) + .build()) + .build(); + + ImmutableList shards = + bundleSharder.shardBundle( + ImmutableList.of(bundleModule), + ImmutableSet.of(OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY), + DEFAULT_METADATA); + + // 1 shards: {x86} x {MDPI}. + assertThat(shards).hasSize(1); + assertThat(shards.stream().map(ModuleSplit::getSplitType).distinct().collect(toImmutableSet())) + .containsExactly(SplitType.STANDALONE); + assertThat(shards).hasSize(1); + + ModuleSplit fatShard = shards.get(0); + assertThat(fatShard.getApkTargeting().getAbiTargeting()).isEqualTo(abiTargeting(X86)); + assertThat(fatShard.getApkTargeting().getScreenDensityTargeting().getValueList()) + .containsExactly(toScreenDensity(DensityAlias.MDPI)); + assertThat(fatShard.getVariantTargeting()).isEqualToDefaultInstance(); + assertThat(fatShard.getSplitType()).isEqualTo(SplitType.STANDALONE); + // The MDPI shard would match both hdpi and ldpi variant of the resource. + assertThat(fatShard.getResourceTable().get()) + .containsResource("com.test.app:drawable/image") + .withConfigSize(2); + assertThat(extractPaths(fatShard.getEntries())) + .containsExactly( + "assets/file.txt", + "dex/classes.dex", + "lib/x86/libtest.so", + "res/drawable-hdpi/image.jpg", + "res/drawable-ldpi/image.jpg", + "root/license.dat"); + } + @Test public void shardByAbiAndDensity_havingManyAbisAndSomeResource_producesManyApks() throws Exception { @@ -775,10 +992,16 @@ public void shardByAbiAndDensity_havingManyAbisAndSomeResource_producesManyApks( targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), targetedNativeDirectory("lib/x86_64", nativeDirectoryTargeting(X86_64)))) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + HDPI_VALUE, + "res/drawable-hdpi/image.jpg")) + .build()) .build(); ImmutableList shards = @@ -897,7 +1120,15 @@ public void shardByAbiAndDensity_havingManyAbisAndSomeResource_producesManyApks( } @Test - public void manyModulesShardByNoDimension_producesFatApk() throws Exception { + @Theory + public void manyModulesShardByNoDimension_producesFatApk( + @FromDataPoints("deviceSpecs") Optional deviceSpec) throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + deviceSpec); BundleModule baseModule = new BundleModuleBuilder("base") .addFile("lib/x86_64/libtest1.so") @@ -1092,8 +1323,15 @@ public void manyModulesShardByDensity_havingNoResources_producesOneApk() throws } @Test - public void manyModulesShardByDensity_havingOnlyOneDensityResource_producesSingleApk() - throws Exception { + @Theory + public void manyModulesShardByDensity_havingOnlyOneDensityResource_producesSingleApk( + @FromDataPoints("deviceSpecs") Optional deviceSpec) throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + deviceSpec); BundleModule baseModule = new BundleModuleBuilder("base") .addFile("res/drawable-hdpi/image.jpg") @@ -1190,10 +1428,16 @@ public void manyModulesShardByAbiAndDensity_havingManyAbisAndSomeResource_produc .addFile("root/license.dat") .setManifest(androidManifest("com.test.app")) .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable/image.jpg", Configuration.getDefaultInstance()), - fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + HDPI_VALUE, + "res/drawable-hdpi/image.jpg")) + .build()) .build(); BundleModule featureModule = new BundleModuleBuilder("feature") @@ -1312,4 +1556,72 @@ public void manyModulesShardByAbiAndDensity_havingManyAbisAndSomeResource_produc } } } + + @Test + public void + manyModulesShardByAbiAndDensity_havingManyAbisAndSomeResourceWithDeviceSpec_producesSingleApk() + throws Exception { + bundleSharder = + new BundleSharder( + tmpDir, + BundleToolVersion.getCurrentVersion(), + /* generate64BitShards= */ true, + Optional.of( + mergeSpecs( + sdkVersion(28), abis("x86"), density(DensityAlias.MDPI), locales("en-US")))); + BundleModule baseModule = + new BundleModuleBuilder("base") + .addFile("assets/file.txt") + .addFile("dex/classes.dex") + .addFile("res/drawable/image.jpg") + .addFile("res/drawable-hdpi/image.jpg") + .addFile("root/license.dat") + .setManifest(androidManifest("com.test.app")) + .setResourceTable( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + DEFAULT_DENSITY_VALUE, + "res/drawable/image.jpg", + HDPI_VALUE, + "res/drawable-hdpi/image.jpg")) + .build()) + .build(); + BundleModule featureModule = + new BundleModuleBuilder("feature") + .addFile("lib/armeabi/libtest.so") + .addFile("lib/x86/libtest.so") + .setManifest(androidManifest("com.test.app")) + .setNativeConfig( + nativeLibraries( + targetedNativeDirectory("lib/armeabi", nativeDirectoryTargeting(ARMEABI)), + targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)))) + .build(); + + ImmutableList shards = + bundleSharder.shardBundle( + ImmutableList.of(baseModule, featureModule), + ImmutableSet.of(OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY), + DEFAULT_METADATA); + + assertThat(shards).hasSize(1); + assertThat(shards.stream().map(ModuleSplit::getSplitType).distinct().collect(toImmutableSet())) + .containsExactly(SplitType.STANDALONE); + ModuleSplit fatShard = shards.get(0); + assertThat(fatShard.getApkTargeting().getAbiTargeting()) + .isEqualTo(abiTargeting(X86, ImmutableSet.of(ARMEABI))); + assertThat(fatShard.getApkTargeting().getScreenDensityTargeting().getValueList()) + .containsExactly(toScreenDensity(DensityAlias.MDPI)); + assertThat(fatShard.getVariantTargeting()).isEqualToDefaultInstance(); + assertThat(fatShard.getSplitType()).isEqualTo(SplitType.STANDALONE); + assertThat(extractPaths(fatShard.getEntries())) + .containsExactly( + "assets/file.txt", + "dex/classes.dex", + "lib/x86/libtest.so", + "res/drawable/image.jpg", + "root/license.dat"); + } } 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 e594d776..bb253d50 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 @@ -16,10 +16,10 @@ 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.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; 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 cd872adf..de78fb79 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,9 +16,9 @@ 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.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import com.android.bundle.Targeting.VariantTargeting; diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitterTest.java index 96a6df54..ffcc46b8 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/LanguageResourcesSplitterTest.java @@ -33,13 +33,16 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.lPlusVariantTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeApkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static junit.framework.TestCase.fail; import com.android.aapt.ConfigurationOuterClass.Configuration; +import com.android.aapt.Resources.ConfigValue; import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.StringPool; import com.android.bundle.Targeting.ApkTargeting; @@ -48,7 +51,11 @@ import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import com.android.tools.build.bundletool.testing.ResourcesTableFactory; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.protobuf.ByteString; import java.util.Collection; @@ -62,6 +69,9 @@ @RunWith(JUnit4.class) public class LanguageResourcesSplitterTest { + private final LanguageResourcesSplitter languageSplitter = + new LanguageResourcesSplitter(/* pinResourceToMaster= */ Predicates.alwaysFalse()); + @Test public void languageResources_split() throws Exception { ResourceTable resourceTable = @@ -80,17 +90,15 @@ public void languageResources_split() throws Exception { .build(); ModuleSplit baseSplit = ModuleSplit.forResources(module); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -147,7 +155,7 @@ public void languageResources_doesntSplitIfAlreadyTargeted() throws Exception { .setMasterSplit(false) .build(); - Collection languageSplits = new LanguageResourcesSplitter().split(densitySplit); + Collection languageSplits = languageSplitter.split(densitySplit); assertThat(languageSplits).hasSize(1); ModuleSplit languageSplit = languageSplits.iterator().next(); assertThat(languageSplit).isEqualTo(densitySplit); @@ -179,18 +187,16 @@ public void languageResources_addsToTargeting() throws Exception { .setMasterSplit(false) .build(); - Collection languageSplits = new LanguageResourcesSplitter().split(densitySplit); + Collection languageSplits = languageSplitter.split(densitySplit); assertThat(languageSplits).hasSize(3); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -247,18 +253,16 @@ public void languageResources_withRegions() throws Exception { .build(); ModuleSplit baseSplit = ModuleSplit.forResources(module); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -298,18 +302,16 @@ public void languageSplits_skipNonResourceEntries() throws Exception { .build(); ModuleSplit baseSplit = ModuleSplit.forModule(module); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -354,18 +356,16 @@ public void languageResources_sourcePoolPreserved() throws Exception { .build(); ModuleSplit baseSplit = ModuleSplit.forResources(module); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -388,18 +388,16 @@ public void languageSplits_defaultLanguageGenerated_noDefaultLanguagePresent() t .build(); ModuleSplit baseSplit = ModuleSplit.forModule(module); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -438,19 +436,17 @@ public void masterSplitFlag_outputMasterSplitInheritsTrue() throws Exception { ModuleSplit baseSplit = ModuleSplit.forResources(module); assertThat(baseSplit.isMasterSplit()).isTrue(); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -490,19 +486,17 @@ public void masterSplitFlag_outputMasterSplitInheritsFalse() throws Exception { .setMasterSplit(false) .build(); - Collection languageSplits = new LanguageResourcesSplitter().split(baseSplit); + Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -527,10 +521,17 @@ public void masterSplitFlag_outputMasterSplitInheritsFalse() throws Exception { @Test public void removingResourceTableFileEntry_removesReferencedFilesFromSplit() throws Exception { ResourceTable resourceTable = - ResourcesTableFactory.createResourceTable( - "image", - fileReference("res/drawable-hdpi/image.jpg", HDPI), - fileReference("res/drawable-fr-hdpi/image.jpg", mergeConfigs(locale("fr"), HDPI))); + new ResourceTableBuilder() + .addPackage("com.test.app") + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + HDPI, + "res/drawable-hdpi/image.jpg", + mergeConfigs(HDPI, locale("fr")), + "res/drawable-fr-hdpi/image.jpg")) + .build(); BundleModule testModule = new BundleModuleBuilder("testModule") @@ -541,19 +542,16 @@ public void removingResourceTableFileEntry_removesReferencedFilesFromSplit() thr .build(); ModuleSplit baseSplit = ModuleSplit.forModule(testModule); - LanguageResourcesSplitter languageSplitter = new LanguageResourcesSplitter(); Collection languageSplits = languageSplitter.split(baseSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -580,10 +578,17 @@ public void removingResourceTableFileEntry_removesReferencedFilesFromSplit() thr @Test public void noEmptySplits_whenNoResourcesLeft() throws Exception { ResourceTable resourceTable = - ResourcesTableFactory.createResourceTable( - "image", - fileReference("res/drawable-en-hdpi/image.jpg", mergeConfigs(locale("en-GB"), HDPI)), - fileReference("res/drawable-fr-hdpi/image.jpg", mergeConfigs(locale("fr"), HDPI))); + new ResourceTableBuilder() + .addPackage("com.test.app") + .addFileResourceForMultipleConfigs( + "drawable", + "image", + ImmutableMap.of( + mergeConfigs(HDPI, locale("en-GB")), + "res/drawable-en-hdpi/image.jpg", + mergeConfigs(HDPI, locale("fr")), + "res/drawable-fr-hdpi/image.jpg")) + .build(); BundleModule testModule = new BundleModuleBuilder("testModule") @@ -594,20 +599,17 @@ public void noEmptySplits_whenNoResourcesLeft() throws Exception { .build(); ModuleSplit resourcesSplit = ModuleSplit.forModule(testModule); - LanguageResourcesSplitter languageSplitter = new LanguageResourcesSplitter(); Collection languageSplits = languageSplitter.split(resourcesSplit); assertThat(languageSplits).hasSize(2); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getSplitType) .distinct() .collect(toImmutableSet())) .containsExactly(SplitType.SPLIT); assertThat( - languageSplits - .stream() + languageSplits.stream() .map(ModuleSplit::getVariantTargeting) .distinct() .collect(toImmutableSet())) @@ -628,12 +630,179 @@ public void noEmptySplits_whenNoResourcesLeft() throws Exception { .containsExactly("res/drawable-fr-hdpi/image.jpg"); } + @Test + public void resourcesPinnedToMasterSplit_noSplitting_strings() throws Exception { + ResourceTable resourceTable = + getStringResourceTable( + "welcome_label", + ImmutableList.of(value("hello", locale("en")), value("bienvenue", locale("fr"))), + "text2", + ImmutableList.of( + value("no worries", locale("en")), value("de rien", locale("fr")))); + + BundleModule module = + new BundleModuleBuilder("testModule") + .setResourceTable(resourceTable) + .setManifest(androidManifest("com.test.app")) + .build(); + ModuleSplit baseSplit = ModuleSplit.forResources(module); + + LanguageResourcesSplitter languageSplitter = + new LanguageResourcesSplitter( + resource -> resource.getResourceId().getFullResourceId() == 0x7f010001); + Collection languageSplits = languageSplitter.split(baseSplit); + ModuleSplit masterSplit = + languageSplits.stream().filter(split -> split.isMasterSplit()).collect(onlyElement()); + assertThat(masterSplit.getResourceTable()) + .hasValue( + getStringResourceTable( + "welcome_label", + 0x01, + ImmutableList.of(value("hello", locale("en")), value("bienvenue", locale("fr"))))); + + ImmutableList configSplits = + languageSplits.stream().filter(split -> !split.isMasterSplit()).collect(toImmutableList()); + ImmutableMap configSplitMap = + Maps.uniqueIndex(configSplits, ModuleSplit::getApkTargeting); + assertThat(configSplitMap.keySet()) + .containsExactly(apkLanguageTargeting("en"), apkLanguageTargeting("fr")); + assertThat(configSplitMap.get(apkLanguageTargeting("en")).getResourceTable()) + .hasValue( + getStringResourceTable( + "text2", 0x02, ImmutableList.of(value("no worries", locale("en"))))); + assertThat(configSplitMap.get(apkLanguageTargeting("fr")).getResourceTable()) + .hasValue( + getStringResourceTable( + "text2", 0x02, ImmutableList.of(value("de rien", locale("fr"))))); + } + + @Test + public void resourcesPinnedToMasterSplit_mixedWithDefaultStrings() throws Exception { + ResourceTable resourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app") + .addStringResource("default_label", "label") + .addStringResourceForMultipleLocales("pinned_label", ImmutableMap.of("en", "yes")) + .addStringResourceForMultipleLocales("split_label", ImmutableMap.of("en", "no")) + .build(); + + BundleModule module = + new BundleModuleBuilder("testModule") + .setResourceTable(resourceTable) + .setManifest(androidManifest("com.test.app")) + .build(); + ModuleSplit baseSplit = ModuleSplit.forResources(module); + + LanguageResourcesSplitter languageSplitter = + new LanguageResourcesSplitter( + resource -> resource.getResourceId().getFullResourceId() == 0x7f010001); // pinned_label + + Collection languageSplits = languageSplitter.split(baseSplit); + assertThat(languageSplits).hasSize(2); + ModuleSplit masterSplit = + languageSplits.stream().filter(split -> split.isMasterSplit()).collect(onlyElement()); + assertThat(masterSplit.getResourceTable()) + .hasValue( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addStringResource("default_label", "label") + .addStringResourceForMultipleLocales("pinned_label", ImmutableMap.of("en", "yes")) + .build()); + + ModuleSplit configSplit = + languageSplits.stream().filter(split -> !split.isMasterSplit()).collect(onlyElement()); + assertThat(configSplit.getApkTargeting()).isEqualTo(apkLanguageTargeting("en")); + assertThat(configSplit.getResourceTable()) + .hasValue( + getStringResourceTable( + "split_label", 0x02, ImmutableList.of(value("no", locale("en"))))); + } + + @Test + public void resourcesPinnedToMasterSplit_noSplitting_fileReferences() throws Exception { + ResourceTable resourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app") + .addResource( + "drawable", + "image", + fileReference("res/drawable-en-hdpi/image.jpg", mergeConfigs(locale("en"), HDPI)), + fileReference("res/drawable-fr-hdpi/image.jpg", mergeConfigs(locale("fr"), HDPI))) + .addResource( + "drawable", + "image2", + fileReference("res/drawable-en-hdpi/image2.jpg", mergeConfigs(locale("en"), HDPI)), + fileReference("res/drawable-fr-hdpi/image2.jpg", mergeConfigs(locale("fr"), HDPI))) + .build(); + BundleModule module = + new BundleModuleBuilder("testModule") + .setResourceTable(resourceTable) + .setManifest(androidManifest("com.test.app")) + .addFile("res/drawable-en-hdpi/image.jpg") + .addFile("res/drawable-fr-hdpi/image.jpg") + .addFile("res/drawable-en-hdpi/image2.jpg") + .addFile("res/drawable-fr-hdpi/image2.jpg") + .build(); + + ModuleSplit baseSplit = ModuleSplit.forResources(module); + + LanguageResourcesSplitter languageSplitter = + new LanguageResourcesSplitter( + resource -> resource.getResourceId().getFullResourceId() == 0x7f010000); + Collection languageSplits = languageSplitter.split(baseSplit); + ModuleSplit masterSplit = + languageSplits.stream().filter(split -> split.isMasterSplit()).collect(onlyElement()); + assertThat(masterSplit.getResourceTable()) + .hasValue( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addResource( + "drawable", + "image", + fileReference( + "res/drawable-en-hdpi/image.jpg", mergeConfigs(locale("en"), HDPI)), + fileReference( + "res/drawable-fr-hdpi/image.jpg", mergeConfigs(locale("fr"), HDPI))) + .build()); + assertThat(extractPaths(masterSplit.getEntries())) + .containsExactly("res/drawable-en-hdpi/image.jpg", "res/drawable-fr-hdpi/image.jpg"); + + ImmutableList configSplits = + languageSplits.stream().filter(split -> !split.isMasterSplit()).collect(toImmutableList()); + ImmutableMap configSplitMap = + Maps.uniqueIndex(configSplits, ModuleSplit::getApkTargeting); + assertThat(configSplitMap.keySet()) + .containsExactly(apkLanguageTargeting("en"), apkLanguageTargeting("fr")); + + ModuleSplit enSplit = configSplitMap.get(apkLanguageTargeting("en")); + assertThat(enSplit.getResourceTable()) + .hasValue( + ResourcesTableFactory.createResourceTable( + /* entryId= */ 0x01, + "image2", + fileReference( + "res/drawable-en-hdpi/image2.jpg", mergeConfigs(locale("en"), HDPI)))); + assertThat(extractPaths(enSplit.getEntries())) + .containsExactly("res/drawable-en-hdpi/image2.jpg"); + + ModuleSplit frSplit = configSplitMap.get(apkLanguageTargeting("fr")); + assertThat(frSplit.getResourceTable()) + .hasValue( + ResourcesTableFactory.createResourceTable( + /* entryId= */ 0x01, + "image2", + fileReference( + "res/drawable-fr-hdpi/image2.jpg", mergeConfigs(locale("fr"), HDPI)))); + assertThat(extractPaths(frSplit.getEntries())) + .containsExactly("res/drawable-fr-hdpi/image2.jpg"); + } + private static ResourceTable getStringResourceTable(Configuration config1, String value1) { return resourceTable( pkg( USER_PACKAGE_OFFSET, "com.test.app", - type(0x01, "strings", entry(0x01, "welcome_label", value(value1, config1))))); + type(0x01, "string", entry(0x01, "welcome_label", value(value1, config1))))); } private static ResourceTable getStringResourceTable( @@ -644,10 +813,35 @@ private static ResourceTable getStringResourceTable( "com.test.app", type( 0x01, - "strings", + "string", entry(0x01, "welcome_label", value(value1, config1), value(value2, config2))))); } + private static ResourceTable getStringResourceTable( + String entry, int entryId, ImmutableList configValues) { + return resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "string", entry(entryId, entry, configValues.toArray(new ConfigValue[0]))))); + } + + private static ResourceTable getStringResourceTable( + String entry, + ImmutableList configValues, + String entry2, + ImmutableList configValues2) { + return resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type( + 0x01, + "string", + entry(0x01, entry, configValues.toArray(new ConfigValue[0])), + entry(0x02, entry2, configValues2.toArray(new ConfigValue[0]))))); + } + private static ResourceTable getStringResourceTable( Configuration config1, String value1, @@ -661,7 +855,7 @@ private static ResourceTable getStringResourceTable( "com.test.app", type( 0x01, - "strings", + "string", entry( 0x01, "welcome_label", 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 4545b92d..69e0fd22 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 @@ -27,6 +27,9 @@ 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.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.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withInstant; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMaxSdkVersion; @@ -72,9 +75,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; import static com.android.tools.build.bundletool.testing.truth.resources.TruthResourceTable.assertThat; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_L_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; @@ -95,7 +95,6 @@ import com.android.bundle.Targeting.AssetsDirectoryTargeting; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.BundleModuleName; @@ -103,11 +102,14 @@ 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; +import com.android.tools.build.bundletool.model.ResourceTableEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +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.BundleModuleBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -117,6 +119,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Predicate; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,6 +134,10 @@ public class ModuleSplitterTest { private static final Version BUNDLETOOL_VERSION = BundleToolVersion.getCurrentVersion(); + private static final Predicate NO_RESOURCES_PINNED_TO_MASTER = + Predicates.alwaysFalse(); + + @Test public void minSdkVersionInOutputTargeting_getsSetToL() throws Exception { BundleModule bundleModule = @@ -740,7 +747,12 @@ public void nativeSplits_64BitLibsDisabled() throws Exception { ModuleSplitter moduleSplitter = new ModuleSplitter( - testModule, BUNDLETOOL_VERSION, withDisabled64BitLibs(), lPlusVariantTargeting()); + testModule, + BUNDLETOOL_VERSION, + withDisabled64BitLibs(), + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER); ImmutableList splits = moduleSplitter.splitModule(); ImmutableMap toTargetingMap = @@ -773,7 +785,9 @@ public void nativeSplits_lPlusTargeting_withAbiAndUncompressNativeLibsSplitter() .setOptimizationDimensions(ImmutableSet.of(ABI)) .setEnableNativeLibraryCompressionSplitter(true) .build(), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER); List splits = moduleSplitter.splitModule(); // Base + X86 Split @@ -810,7 +824,9 @@ public void nativeSplits_mPlusTargeting_withAbiAndUncompressNativeLibsSplitter() .setOptimizationDimensions(ImmutableSet.of(ABI)) .setEnableNativeLibraryCompressionSplitter(true) .build(), - variantMinSdkTargeting(ANDROID_M_API_VERSION)); + variantMinSdkTargeting(ANDROID_M_API_VERSION), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER); List splits = moduleSplitter.splitModule(); // Base + X86 Split @@ -878,7 +894,9 @@ public void nativeSplits_pPlusTargeting_withDexCompressionSplitter() throws Exce testModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setEnableDexCompressionSplitter(true).build(), - variantMinSdkTargeting(ANDROID_P_API_VERSION)); + variantMinSdkTargeting(ANDROID_P_API_VERSION), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER); List splits = moduleSplitter.splitModule(); assertThat(splits).hasSize(1); @@ -902,7 +920,9 @@ public void nativeSplits_lPlusTargeting_withDexCompressionSplitter() throws Exce testModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setEnableDexCompressionSplitter(true).build(), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER); List splits = moduleSplitter.splitModule(); assertThat(splits).hasSize(1); @@ -1173,7 +1193,8 @@ public void splitNameRemovedforInstalledSplit() throws Exception { @Test public void splitNameNotRemovedforInstantSplit() throws Exception { - XmlNode manifest = androidManifest("com.test.app", withSplitNameActivity("FooActivity", "foo")); + XmlNode manifest = + androidManifest("com.test.app", withSplitNameActivity("FooActivity", "testModule")); BundleModule bundleModule = new BundleModuleBuilder("testModule").setManifest(manifest).build(); ImmutableList moduleSplits = @@ -1181,7 +1202,9 @@ public void splitNameNotRemovedforInstantSplit() throws Exception { bundleModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setForInstantAppVariants(true).build(), - lPlusVariantTargeting()) + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER) .splitModule(); assertThat(moduleSplits).hasSize(1); @@ -1200,7 +1223,42 @@ public void splitNameNotRemovedforInstantSplit() throws Exception { assertThat(activityElement.getAttributeList()) .containsExactly( xmlAttribute(ANDROID_NAMESPACE_URI, "name", NAME_RESOURCE_ID, "FooActivity"), - xmlAttribute(ANDROID_NAMESPACE_URI, "splitName", SPLIT_NAME_RESOURCE_ID, "foo")); + xmlAttribute(ANDROID_NAMESPACE_URI, "splitName", SPLIT_NAME_RESOURCE_ID, "testModule")); + } + + @Test + public void nonInstantActivityRemovedforInstantManifest() throws Exception { + XmlNode manifest = + androidManifest("com.test.app", withSplitNameActivity("FooActivity", "onDemandModule")); + BundleModule bundleModule = + new BundleModuleBuilder("onDemandModule").setManifest(manifest).build(); + + ImmutableList moduleSplits = + new ModuleSplitter( + bundleModule, + BUNDLETOOL_VERSION, + ApkGenerationConfiguration.builder().setForInstantAppVariants(true).build(), + lPlusVariantTargeting(), + ImmutableSet.of(), + NO_RESOURCES_PINNED_TO_MASTER) + .splitModule(); + + assertThat(moduleSplits).hasSize(1); + ModuleSplit masterSplit = moduleSplits.get(0); + ImmutableList activities = + masterSplit + .getAndroidManifest() + .getManifestRoot() + .getElement() + .getChildElement("application") + .getChildrenElements(ACTIVITY_ELEMENT_NAME) + .map(XmlProtoElement::getProto) + .collect(toImmutableList()); + assertThat(activities).hasSize(1); + XmlElement activityElement = activities.get(0); + assertThat(activityElement.getAttributeList()) + .containsExactly( + xmlAttribute(ANDROID_NAMESPACE_URI, "name", NAME_RESOURCE_ID, "MainActivity")); } @Test @@ -1215,7 +1273,9 @@ public void instantManifestChanges_addsMinSdkVersion() throws Exception { bundleModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setForInstantAppVariants(true).build(), - lPlusVariantTargeting()) + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER) .splitModule(); assertThat(moduleSplits).hasSize(1); @@ -1240,7 +1300,9 @@ public void instantManifestChanges_keepsMinSdkVersion() throws Exception { bundleModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setForInstantAppVariants(true).build(), - lPlusVariantTargeting()) + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER) .splitModule(); assertThat(moduleSplits).hasSize(1); @@ -1265,7 +1327,9 @@ public void instantManifestChanges_updatesMinSdkVersion() throws Exception { bundleModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setForInstantAppVariants(true).build(), - lPlusVariantTargeting()) + lPlusVariantTargeting(), + ImmutableSet.of("testModule"), + NO_RESOURCES_PINNED_TO_MASTER) .splitModule(); assertThat(moduleSplits).hasSize(1); @@ -1345,7 +1409,9 @@ public void addingLibraryPlaceholders_baseModule() throws Exception { .setAbisForPlaceholderLibs( ImmutableSet.of(toAbi(AbiAlias.X86), toAbi(AbiAlias.ARM64_V8A))) .build(), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of("base"), + NO_RESOURCES_PINNED_TO_MASTER); ImmutableList splits = moduleSplitter.splitModule(); assertThat(splits).hasSize(1); @@ -1372,7 +1438,9 @@ public void addingLibraryPlaceholders_featureModule_noAction() throws Exception .setAbisForPlaceholderLibs( ImmutableSet.of(toAbi(AbiAlias.X86), toAbi(AbiAlias.ARM64_V8A))) .build(), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of("feature"), + NO_RESOURCES_PINNED_TO_MASTER); ImmutableList splits = moduleSplitter.splitModule(); assertThat(splits).hasSize(1); @@ -1399,7 +1467,9 @@ private static ModuleSplitter createAbiAndDensitySplitter(BundleModule module) { module, BUNDLETOOL_VERSION, withOptimizationDimensions(ImmutableSet.of(ABI, SCREEN_DENSITY)), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of(module.getName().getName()), + NO_RESOURCES_PINNED_TO_MASTER); } private static ModuleSplitter createAbiDensityAndLanguageSplitter(BundleModule module) { @@ -1407,7 +1477,9 @@ private static ModuleSplitter createAbiDensityAndLanguageSplitter(BundleModule m module, BUNDLETOOL_VERSION, withOptimizationDimensions(ImmutableSet.of(ABI, SCREEN_DENSITY, LANGUAGE)), - lPlusVariantTargeting()); + lPlusVariantTargeting(), + ImmutableSet.of(module.getName().getName()), + NO_RESOURCES_PINNED_TO_MASTER); } private static ApkGenerationConfiguration withOptimizationDimensions( diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitterTest.java index deb3b639..6459ecaa 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibrariesCompressionSplitterTest.java @@ -17,6 +17,7 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.ManifestMutator.withExtractNativeLibs; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_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; @@ -24,7 +25,6 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGeneratorTest.java index 5e8d70f0..f4f32233 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGeneratorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/NativeLibsCompressionVariantGeneratorTest.java @@ -16,12 +16,12 @@ 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.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries; import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import com.android.bundle.Files.NativeLibraries; diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/RemoteAssetModuleSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/RemoteAssetModuleSplitterTest.java new file mode 100755 index 00000000..94cc3d7b --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/splitters/RemoteAssetModuleSplitterTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 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.splitters; + +import static com.android.tools.build.bundletool.model.OptimizationDimension.LANGUAGE; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withTypeAttribute; +import static com.android.tools.build.bundletool.testing.TargetingUtils.apkGraphicsTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.apkLanguageTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.apkTextureTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.assets; +import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.graphicsApiTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.languageTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeApkTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.mergeAssetsTargeting; +import static com.android.tools.build.bundletool.testing.TargetingUtils.openGlVersionFrom; +import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedAssetsDirectory; +import static com.android.tools.build.bundletool.testing.TargetingUtils.textureCompressionTargeting; +import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; + +import com.android.bundle.Targeting.ApkTargeting; +import com.android.bundle.Targeting.AssetsDirectoryTargeting; +import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; +import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModule.ModuleType; +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; +import com.android.tools.build.bundletool.testing.BundleModuleBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RemoteAssetModuleSplitterTest { + + + @Test + public void singleSlice() throws Exception { + BundleModule testModule = + new BundleModuleBuilder("testModule") + .addFile("assets/image.jpg") + .addFile("assets/image2.jpg") + .setManifest(androidManifest("com.test.app", withTypeAttribute("remote-asset"))) + .build(); + + assertThat(testModule.getModuleType()).isEqualTo(ModuleType.ASSET_MODULE); + ImmutableList slices = + new RemoteAssetModuleSplitter(testModule, ApkGenerationConfiguration.getDefaultInstance()) + .splitModule(); + + assertThat(slices).hasSize(1); + ModuleSplit masterSlice = slices.get(0); + assertThat(masterSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(masterSlice.isMasterSplit()).isTrue(); + assertThat(masterSlice.getApkTargeting()).isEqualToDefaultInstance(); + assertThat(extractPaths(masterSlice.getEntries())) + .containsExactly("assets/image.jpg", "assets/image2.jpg"); + } + + @Test + public void slicesByLanguage() throws Exception { + BundleModule testModule = + new BundleModuleBuilder("testModule") + .addFile("assets/images/image.jpg") + .addFile("assets/images#lang_en/image.jpg") + .addFile("assets/images#lang_es/image.jpg") + .setAssetsConfig( + assets( + targetedAssetsDirectory( + "assets/images", AssetsDirectoryTargeting.getDefaultInstance()), + targetedAssetsDirectory( + "assets/images#lang_es", assetsDirectoryTargeting(languageTargeting("es"))), + targetedAssetsDirectory( + "assets/images#lang_en", + assetsDirectoryTargeting(languageTargeting("en"))))) + .setManifest(androidManifest("com.test.app", withTypeAttribute("remote-asset"))) + .build(); + + assertThat(testModule.getModuleType()).isEqualTo(ModuleType.ASSET_MODULE); + ImmutableList slices = + new RemoteAssetModuleSplitter( + testModule, + ApkGenerationConfiguration.builder() + .setOptimizationDimensions(ImmutableSet.of(LANGUAGE)) + .build()) + .splitModule(); + + assertThat(slices).hasSize(3); + + Map slicesByTargeting = + Maps.uniqueIndex(slices, ModuleSplit::getApkTargeting); + + assertThat(slicesByTargeting.keySet()) + .containsExactly( + ApkTargeting.getDefaultInstance(), + apkLanguageTargeting("es"), + apkLanguageTargeting("en")); + + ModuleSplit defaultSlice = slicesByTargeting.get(ApkTargeting.getDefaultInstance()); + assertThat(defaultSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(defaultSlice.isMasterSplit()).isTrue(); + assertThat(extractPaths(defaultSlice.getEntries())).containsExactly("assets/images/image.jpg"); + + ModuleSplit esSlice = slicesByTargeting.get(apkLanguageTargeting("es")); + assertThat(esSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(esSlice.isMasterSplit()).isFalse(); + assertThat(extractPaths(esSlice.getEntries())) + .containsExactly("assets/images#lang_es/image.jpg"); + + ModuleSplit enSlice = slicesByTargeting.get(apkLanguageTargeting("en")); + assertThat(enSlice.getSplitType()).isEqualTo(SplitType.ASSET_SLICE); + assertThat(enSlice.isMasterSplit()).isFalse(); + assertThat(extractPaths(enSlice.getEntries())) + .containsExactly("assets/images#lang_en/image.jpg"); + } + +} diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitterTest.java index 824e2122..d97f2292 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/ScreenDensityResourcesSplitterTest.java @@ -17,6 +17,14 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.ManifestMutator.withSplitsRequired; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.HDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.LDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.TVDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XHDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XXHDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XXXHDPI_VALUE; import static com.android.tools.build.bundletool.splitters.ScreenDensityResourcesSplitter.DEFAULT_DENSITY_BUCKETS; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.compareManifestMutators; @@ -38,13 +46,11 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.apkDensityTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.assertForNonDefaultSplits; import static com.android.tools.build.bundletool.testing.TargetingUtils.assertForSingleDefaultSplit; +import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; import static com.android.tools.build.bundletool.testing.truth.resources.TruthResourceTable.assertThat; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DEFAULT_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.HDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.MDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.XXXHDPI_VALUE; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; @@ -58,18 +64,21 @@ import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleSplit; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +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.BundleModuleBuilder; -import com.android.tools.build.bundletool.testing.ResourcesTableFactory; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.MoreCollectors; import com.google.common.collect.Sets; import com.google.common.truth.Truth8; import com.google.protobuf.ByteString; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,8 +87,12 @@ @RunWith(JUnit4.class) public class ScreenDensityResourcesSplitterTest { + private static final Predicate NO_RESOURCES_PINNED_TO_MASTER = + Predicates.alwaysFalse(); + private final ScreenDensityResourcesSplitter splitter = - new ScreenDensityResourcesSplitter(BundleToolVersion.getCurrentVersion()); + new ScreenDensityResourcesSplitter( + BundleToolVersion.getCurrentVersion(), NO_RESOURCES_PINNED_TO_MASTER); @Test public void noResourceTable_noResourceSplits() throws Exception { @@ -131,10 +144,16 @@ public void allSplitsPresentWithResourceTable() throws Exception { .addFile("res/drawable-mdpi/image.jpg") .addFile("res/drawable-hdpi/image.jpg") .setResourceTable( - ResourcesTableFactory.createResourceTable( - "image", - fileReference("res/drawable-mdpi/image.jpg", MDPI), - fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app")) .build(); ImmutableSet densities = @@ -245,8 +264,11 @@ public void preservesSourcePool() throws Exception { StringPool sourcePool = StringPool.newBuilder().setData(ByteString.copyFrom(new byte[] {'x'})).build(); ResourceTable table = - ResourcesTableFactory.createResourceTable( - "image", fileReference("res/drawable-mdpi/image.jpg", MDPI)) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", ImmutableMap.of(MDPI_VALUE, "res/drawable-mdpi/image.jpg")) + .build() .toBuilder() .setSourcePool(sourcePool) .build(); @@ -270,15 +292,20 @@ public void preservesSourcePool() throws Exception { @Test public void picksTheResourceForExactDensity() throws Exception { ResourceTable table = - ResourcesTableFactory.createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-mdpi/image.jpg", MDPI), - fileReference("res/drawable-tvdpi/image.jpg", TVDPI), - fileReference("res/drawable-hdpi/image.jpg", HDPI), - fileReference("res/drawable-xhdpi/image.jpg", XHDPI), - fileReference("res/drawable-xxhdpi/image.jpg", XXHDPI), - fileReference("res/drawable-xxxhdpi/image.jpg", XXXHDPI)); + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.builder() + .put(LDPI_VALUE, "res/drawable-ldpi/image.jpg") + .put(MDPI_VALUE, "res/drawable-mdpi/image.jpg") + .put(TVDPI_VALUE, "res/drawable-tvdpi/image.jpg") + .put(HDPI_VALUE, "res/drawable-hdpi/image.jpg") + .put(XHDPI_VALUE, "res/drawable-xhdpi/image.jpg") + .put(XXHDPI_VALUE, "res/drawable-xxhdpi/image.jpg") + .put(XXXHDPI_VALUE, "res/drawable-xxxhdpi/image.jpg") + .build()) + .build(); BundleModule testModule = new BundleModuleBuilder("testModule") @@ -469,7 +496,8 @@ public void densityBucket_neighbouringResources_edgeCase() throws Exception { ScreenDensityResourcesSplitter splitter = new ScreenDensityResourcesSplitter( ImmutableSet.of(DensityAlias.XXHDPI, DensityAlias.XXXHDPI), - BundleToolVersion.getCurrentVersion()); + BundleToolVersion.getCurrentVersion(), + NO_RESOURCES_PINNED_TO_MASTER); ImmutableCollection densitySplits = splitter.split(ModuleSplit.forResources(module)); @@ -658,7 +686,7 @@ public void defaultDensityWithAlternatives_before_0_4_0() throws Exception { .build(); ScreenDensityResourcesSplitter splitter = - new ScreenDensityResourcesSplitter(Version.of("0.3.3")); + new ScreenDensityResourcesSplitter(Version.of("0.3.3"), NO_RESOURCES_PINNED_TO_MASTER); ImmutableCollection splits = splitter.split(ModuleSplit.forResources(module)); // Master split: Resource present with default targeting. @@ -767,7 +795,7 @@ public void nonDefaultDensityResourceWithoutAlternatives_before_0_4_0() throws E .build(); ScreenDensityResourcesSplitter splitter = - new ScreenDensityResourcesSplitter(Version.of("0.3.3")); + new ScreenDensityResourcesSplitter(Version.of("0.3.3"), NO_RESOURCES_PINNED_TO_MASTER); ImmutableCollection splits = splitter.split(ModuleSplit.forResources(module)); // 1 base + 7 config splits @@ -827,10 +855,16 @@ public void manifestMutatorToRequireSplits_registered_whenDensityResourcesPresen .addFile("res/drawable-mdpi/image.jpg") .addFile("res/drawable-hdpi/image.jpg") .setResourceTable( - ResourcesTableFactory.createResourceTable( - "image", - fileReference("res/drawable-mdpi/image.jpg", MDPI), - fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + MDPI_VALUE, + "res/drawable-ldpi/image.jpg", + HDPI_VALUE, + "res/drawable-dpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app")) .build(); @@ -848,6 +882,55 @@ public void manifestMutatorToRequireSplits_registered_whenDensityResourcesPresen } } + @Test + public void resourcesPinnedToMaster_splittingSupressed() throws Exception { + BundleModule testModule = + new BundleModuleBuilder("testModule") + .addFile("res/drawable-mdpi/image.jpg") + .addFile("res/drawable-hdpi/image.jpg") + .addFile("res/drawable-mdpi/image2.jpg") + .addFile("res/drawable-hdpi/image2.jpg") + .setResourceTable( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + /* mdpi */ 160, "res/drawable-mdpi/image.jpg", + /* hdpi */ 240, "res/drawable-hdpi/image.jpg")) + .addDrawableResourceForMultipleDensities( + "image2", + ImmutableMap.of( + /* mdpi */ 160, "res/drawable-mdpi/image2.jpg", + /* hdpi */ 240, "res/drawable-hdpi/image2.jpg")) + .build()) + .setManifest(androidManifest("com.test.app")) + .build(); + + Predicate masterResourcesPredicate = + resource -> resource.getResourceId().getFullResourceId() == 0x7f010000; + ScreenDensityResourcesSplitter splitter = + new ScreenDensityResourcesSplitter( + BundleToolVersion.getCurrentVersion(), masterResourcesPredicate); + + ImmutableCollection densitySplits = + splitter.split(ModuleSplit.forResources(testModule)); + + ImmutableList configSplits = + densitySplits.stream().filter(split -> !split.isMasterSplit()).collect(toImmutableList()); + assertThat(configSplits).isNotEmpty(); + for (ModuleSplit configSplit : configSplits) { + assertThat(extractPaths(configSplit.getEntries())) + .doesNotContain("res/drawable-mdpi/image.jpg"); + assertThat(extractPaths(configSplit.getEntries())) + .doesNotContain("res/drawable-hdpi/image.jpg"); + } + ModuleSplit masterSplit = + densitySplits.stream().filter(split -> split.isMasterSplit()).collect(onlyElement()); + assertThat(extractPaths(masterSplit.getEntries())) + .containsExactly("res/drawable-mdpi/image.jpg", "res/drawable-hdpi/image.jpg"); + } + private static ModuleSplit findModuleSplitWithScreenDensityTargeting( ImmutableCollection moduleSplits, DensityAlias densityAlias) { return findModuleSplitWithScreenDensityTargeting( @@ -856,21 +939,19 @@ private static ModuleSplit findModuleSplitWithScreenDensityTargeting( private static ModuleSplit findModuleSplitWithScreenDensityTargeting( ImmutableCollection moduleSplits, ScreenDensity density) { - return moduleSplits - .stream() + return moduleSplits.stream() .filter( split -> split.getApkTargeting().getScreenDensityTargeting().getValueCount() > 0 && density.equals( split.getApkTargeting().getScreenDensityTargeting().getValue(0))) - .collect(MoreCollectors.onlyElement()); + .collect(onlyElement()); } private static ModuleSplit findModuleSplitWithDefaultTargeting( ImmutableCollection moduleSplits) { - return moduleSplits - .stream() + return moduleSplits.stream() .filter(split -> split.getApkTargeting().equals(ApkTargeting.getDefaultInstance())) - .collect(MoreCollectors.onlyElement()); + .collect(onlyElement()); } } 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 91910742..a98ccfe4 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 @@ -16,15 +16,21 @@ package com.android.tools.build.bundletool.splitters; +import static com.android.bundle.Targeting.Abi.AbiAlias.X86; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.LDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MDPI_VALUE; +import static com.android.tools.build.bundletool.testing.DeviceFactory.abis; +import static com.android.tools.build.bundletool.testing.DeviceFactory.density; +import static com.android.tools.build.bundletool.testing.DeviceFactory.locales; +import static com.android.tools.build.bundletool.testing.DeviceFactory.mergeSpecs; +import static com.android.tools.build.bundletool.testing.DeviceFactory.sdkVersion; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.LDPI; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.MDPI; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.createResourceTable; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.fileReference; +import static com.android.tools.build.bundletool.testing.TargetingUtils.abiTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries; import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; import static com.android.tools.build.bundletool.testing.TargetingUtils.toAbi; +import static com.android.tools.build.bundletool.testing.TargetingUtils.toScreenDensity; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; @@ -34,18 +40,22 @@ import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.AbiTargeting; import com.android.bundle.Targeting.ApkTargeting; +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.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.android.tools.build.bundletool.optimizations.ApkOptimizations; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.nio.file.Path; import java.util.List; +import java.util.Optional; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -92,9 +102,7 @@ public void simpleMultipleModules( .build()); ImmutableList moduleSplits = - new ShardedApksGenerator( - tmpDir, BUNDLETOOL_VERSION, standaloneSplitType, /* generate64BitShards= */ true) - .generateSplits(bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS); + generateModuleSplits(bundleModule, standaloneSplitType, /* generate64BitShards= */ true); assertThat(moduleSplits).hasSize(1); ModuleSplit moduleSplit = moduleSplits.get(0); @@ -116,24 +124,28 @@ public void singleModule_withNativeLibsAndDensity( .addFile("lib/x86_64/libsome.so") .setNativeConfig( nativeLibraries( - targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(AbiAlias.X86)), + targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), targetedNativeDirectory( "lib/x86_64", nativeDirectoryTargeting(AbiAlias.X86_64)))) // Add some density-specific resources. .addFile("res/drawable-ldpi/image.jpg") .addFile("res/drawable-mdpi/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app")) .build()); ImmutableList moduleSplits = - new ShardedApksGenerator( - tmpDir, BUNDLETOOL_VERSION, standaloneSplitType, /* generate64BitShards= */ true) - .generateSplits(bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS); + generateModuleSplits(bundleModule, standaloneSplitType, /* generate64BitShards= */ true); assertThat(moduleSplits).hasSize(14); // 7 (density), 2 (abi) splits assertThat(moduleSplits.stream().map(ModuleSplit::getSplitType).collect(toImmutableSet())) @@ -152,24 +164,28 @@ public void singleModule_withNativeLibsAndDensity_64bitNativeLibsDisabled( .addFile("lib/x86_64/libsome.so") .setNativeConfig( nativeLibraries( - targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(AbiAlias.X86)), + targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), targetedNativeDirectory( "lib/x86_64", nativeDirectoryTargeting(AbiAlias.X86_64)))) // Add some density-specific resources. .addFile("res/drawable-ldpi/image.jpg") .addFile("res/drawable-mdpi/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-mdpi/image.jpg", MDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app")) .build()); ImmutableList moduleSplits = - new ShardedApksGenerator( - tmpDir, BUNDLETOOL_VERSION, standaloneSplitType, /* generate64BitShards= */ false) - .generateSplits(bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS); + generateModuleSplits(bundleModule, standaloneSplitType, /* generate64BitShards= */ false); assertThat(moduleSplits).hasSize(7); // 7 (density), 1 (abi) split // Verify that the only ABI is x86. @@ -180,7 +196,7 @@ public void singleModule_withNativeLibsAndDensity_64bitNativeLibsDisabled( .map(AbiTargeting::getValueList) .flatMap(List::stream) .collect(toImmutableSet()); - assertThat(abiTargetings).containsExactly(toAbi(AbiAlias.X86)); + assertThat(abiTargetings).containsExactly(toAbi(X86)); // And ABI has no alternatives. ImmutableSet abiAlternatives = moduleSplits.stream() @@ -194,6 +210,76 @@ public void singleModule_withNativeLibsAndDensity_64bitNativeLibsDisabled( .containsExactly(standaloneSplitType); } + @Test + public void singleModule_withNativeLibsAndDensityWithDeviceSpec_64bitNativeLibsDisabled() + throws Exception { + + ImmutableList bundleModule = + ImmutableList.of( + new BundleModuleBuilder("base") + .addFile("lib/x86/libsome.so") + .addFile("lib/x86_64/libsome.so") + .setNativeConfig( + nativeLibraries( + targetedNativeDirectory("lib/x86", nativeDirectoryTargeting(X86)), + targetedNativeDirectory( + "lib/x86_64", nativeDirectoryTargeting(AbiAlias.X86_64)))) + // Add some density-specific resources. + .addFile("res/drawable-ldpi/image.jpg") + .addFile("res/drawable-mdpi/image.jpg") + .setResourceTable( + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + MDPI_VALUE, + "res/drawable-mdpi/image.jpg")) + .build()) + .setManifest(androidManifest("com.test.app")) + .build()); + + ImmutableList moduleSplits = + new ShardedApksGenerator( + tmpDir, BUNDLETOOL_VERSION, SplitType.SYSTEM, /* generate64BitShards= */ false) + .generateSystemSplits( + bundleModule, + DEFAULT_METADATA, + DEFAULT_APK_OPTIMIZATIONS, + Optional.of( + mergeSpecs( + sdkVersion(28), + abis("x86"), + density(DensityAlias.MDPI), + locales("en-US")))); + + assertThat(moduleSplits).hasSize(1); // 1 (density), 1 (abi) split + ModuleSplit moduleSplit = moduleSplits.get(0); + assertThat(moduleSplit.getApkTargeting().getAbiTargeting()).isEqualTo(abiTargeting(X86)); + assertThat(moduleSplit.getApkTargeting().getScreenDensityTargeting().getValueList()) + .containsExactly(toScreenDensity(DensityAlias.MDPI)); + assertThat(moduleSplits.stream().map(ModuleSplit::getSplitType).collect(toImmutableSet())) + .containsExactly(SplitType.SYSTEM); + } + + private ImmutableList generateModuleSplits( + ImmutableList bundleModule, + SplitType standaloneSplitType, + boolean generate64BitShards) { + if (standaloneSplitType.equals(SplitType.STANDALONE)) { + return new ShardedApksGenerator( + tmpDir, BUNDLETOOL_VERSION, standaloneSplitType, generate64BitShards) + .generateSplits(bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS); + } else { + return new ShardedApksGenerator( + tmpDir, BUNDLETOOL_VERSION, standaloneSplitType, generate64BitShards) + .generateSystemSplits( + bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS, Optional.empty()); + } + } + 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 c9069161..42a1ad53 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 @@ -16,15 +16,16 @@ package com.android.tools.build.bundletool.splitters; +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.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.lPlusVariantTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries; import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_L_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; @@ -34,13 +35,15 @@ import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; +import com.android.tools.build.bundletool.model.ResourceTableEntry; +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.BundleModuleBuilder; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; +import java.util.function.Predicate; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,6 +53,9 @@ public class SplitApksGeneratorTest { private static final Version BUNDLETOOL_VERSION = BundleToolVersion.getCurrentVersion(); + private static final Predicate NO_RESOURCES_PINNED_TO_MASTER = + Predicates.alwaysFalse(); + @Test public void simpleMultipleModules() throws Exception { @@ -66,7 +72,10 @@ public void simpleMultipleModules() throws Exception { ImmutableList moduleSplits = new SplitApksGenerator( - bundleModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.getDefaultInstance()) + bundleModule, + BUNDLETOOL_VERSION, + ApkGenerationConfiguration.getDefaultInstance(), + NO_RESOURCES_PINNED_TO_MASTER) .generateSplits(); assertThat(moduleSplits).hasSize(2); @@ -75,12 +84,12 @@ public void simpleMultipleModules() throws Exception { ModuleSplit baseModule = moduleSplitMap.get("base"); assertThat(baseModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(baseModule)).containsExactly("assets/leftover.txt"); + assertThat(extractPaths(baseModule.getEntries())).containsExactly("assets/leftover.txt"); assertThat(baseModule.getVariantTargeting()).isEqualTo(lPlusVariantTargeting()); ModuleSplit testModule = moduleSplitMap.get("test"); assertThat(testModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(testModule)).containsExactly("assets/test.txt"); + assertThat(extractPaths(testModule.getEntries())).containsExactly("assets/test.txt"); assertThat(testModule.getVariantTargeting()).isEqualTo(lPlusVariantTargeting()); } @@ -109,7 +118,8 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibraries() throws Excep BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder() .setEnableNativeLibraryCompressionSplitter(true) - .build()) + .build(), + NO_RESOURCES_PINNED_TO_MASTER) .generateSplits(); VariantTargeting lVariantTargeting = @@ -129,23 +139,23 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibraries() throws Excep ModuleSplit baseLModule = getModuleSplit(moduleSplits, lVariantTargeting, "base"); assertThat(baseLModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(baseLModule)) + assertThat(extractPaths(baseLModule.getEntries())) .containsExactly("assets/leftover.txt", "lib/x86_64/libsome.so"); assertThat(isCompressed(baseLModule, "lib/x86_64/libsome.so")).isTrue(); ModuleSplit testLModule = getModuleSplit(moduleSplits, lVariantTargeting, "test"); assertThat(testLModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(testLModule)).containsExactly("assets/test.txt"); + assertThat(extractPaths(testLModule.getEntries())).containsExactly("assets/test.txt"); ModuleSplit baseMModule = getModuleSplit(moduleSplits, mVariantTargeting, "base"); assertThat(baseMModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(baseMModule)) + assertThat(extractPaths(baseMModule.getEntries())) .containsExactly("assets/leftover.txt", "lib/x86_64/libsome.so"); assertThat(isCompressed(baseMModule, "lib/x86_64/libsome.so")).isFalse(); ModuleSplit testMModule = getModuleSplit(moduleSplits, mVariantTargeting, "test"); assertThat(testMModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(testMModule)).containsExactly("assets/test.txt"); + assertThat(extractPaths(testMModule.getEntries())).containsExactly("assets/test.txt"); } @Test @@ -176,7 +186,8 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith ApkGenerationConfiguration.builder() .setEnableNativeLibraryCompressionSplitter(true) .setEnableDexCompressionSplitter(true) - .build()) + .build(), + NO_RESOURCES_PINNED_TO_MASTER) .generateSplits(); VariantTargeting lVariantTargeting = @@ -203,35 +214,38 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith ModuleSplit baseLModule = getModuleSplit(moduleSplits, lVariantTargeting, "base"); assertThat(baseLModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(baseLModule)) + assertThat(extractPaths(baseLModule.getEntries())) .containsExactly("assets/leftover.txt", "lib/x86_64/libsome.so"); assertThat(isCompressed(baseLModule, "lib/x86_64/libsome.so")).isTrue(); ModuleSplit testLModule = getModuleSplit(moduleSplits, lVariantTargeting, "test"); assertThat(testLModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(testLModule)).containsExactly("assets/test.txt", "dex/classes.dex"); + assertThat(extractPaths(testLModule.getEntries())) + .containsExactly("assets/test.txt", "dex/classes.dex"); assertThat(isCompressed(testLModule, "dex/classes.dex")).isTrue(); ModuleSplit baseMModule = getModuleSplit(moduleSplits, mVariantTargeting, "base"); assertThat(baseMModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(baseMModule)) + assertThat(extractPaths(baseMModule.getEntries())) .containsExactly("assets/leftover.txt", "lib/x86_64/libsome.so"); assertThat(isCompressed(baseMModule, "lib/x86_64/libsome.so")).isFalse(); ModuleSplit testMModule = getModuleSplit(moduleSplits, mVariantTargeting, "test"); assertThat(testMModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(testMModule)).containsExactly("assets/test.txt", "dex/classes.dex"); + assertThat(extractPaths(testMModule.getEntries())) + .containsExactly("assets/test.txt", "dex/classes.dex"); assertThat(isCompressed(testMModule, "dex/classes.dex")).isTrue(); ModuleSplit basePModule = getModuleSplit(moduleSplits, pVariantTargeting, "base"); assertThat(basePModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(basePModule)) + 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"); assertThat(testPModule.getSplitType()).isEqualTo(SplitType.SPLIT); - assertThat(getEntriesPaths(testPModule)).containsExactly("assets/test.txt", "dex/classes.dex"); + assertThat(extractPaths(testPModule.getEntries())) + .containsExactly("assets/test.txt", "dex/classes.dex"); assertThat(isCompressed(testPModule, "dex/classes.dex")).isFalse(); } @@ -265,7 +279,8 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith .setEnableNativeLibraryCompressionSplitter(true) .setEnableDexCompressionSplitter(true) .setForInstantAppVariants(true) - .build()) + .build(), + NO_RESOURCES_PINNED_TO_MASTER) .generateSplits(); // 2 splits for L variant @@ -275,14 +290,15 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith ModuleSplit baseModule = moduleSplitMap.get("base"); assertThat(baseModule.getSplitType()).isEqualTo(SplitType.INSTANT); - assertThat(getEntriesPaths(baseModule)) + assertThat(extractPaths(baseModule.getEntries())) .containsExactly("assets/leftover.txt", "lib/x86_64/libsome.so"); assertThat(baseModule.getVariantTargeting()).isEqualTo(lPlusVariantTargeting()); assertThat(isCompressed(baseModule, "lib/x86_64/libsome.so")).isFalse(); ModuleSplit testModule = moduleSplitMap.get("test"); assertThat(testModule.getSplitType()).isEqualTo(SplitType.INSTANT); - assertThat(getEntriesPaths(testModule)).containsExactly("assets/test.txt", "dex/classes.dex"); + assertThat(extractPaths(testModule.getEntries())) + .containsExactly("assets/test.txt", "dex/classes.dex"); assertThat(testModule.getVariantTargeting()).isEqualTo(lPlusVariantTargeting()); assertThat(isCompressed(testModule, "dex/classes.dex")).isTrue(); } @@ -298,12 +314,6 @@ private static ModuleSplit getModuleSplit( .get(); } - private static ImmutableSet getEntriesPaths(ModuleSplit moduleSplit) { - return moduleSplit.getEntries().stream() - .map(moduleEntry -> moduleEntry.getPath().toString()) - .collect(toImmutableSet()); - } - private static boolean isCompressed(ModuleSplit moduleSplit, String path) { return moduleSplit.findEntry(path).get().shouldCompress(); } diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/SplittingPipelineTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/SplittingPipelineTest.java index d63b5a0c..573d0c97 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/SplittingPipelineTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/SplittingPipelineTest.java @@ -64,7 +64,7 @@ public void testAllSplittersCalled() { TrivialSplitter splitter = new TrivialSplitter(); TrivialSplitter splitter2 = new TrivialSplitter(); - SplittingPipeline pipeline = SplittingPipeline.create(ImmutableList.of(splitter, splitter2)); + SplittingPipeline pipeline = new SplittingPipeline(ImmutableList.of(splitter, splitter2)); ImmutableCollection splits = pipeline.split(baseSplit); assertThat(splitter.splitCallCount).isEqualTo(1); @@ -77,7 +77,7 @@ public void testSplittersCalledInRightOrder() { DoublingSplitter splitter = new DoublingSplitter(); DoublingSplitter splitter2 = new DoublingSplitter(); - SplittingPipeline pipeline = SplittingPipeline.create(ImmutableList.of(splitter, splitter2)); + SplittingPipeline pipeline = new SplittingPipeline(ImmutableList.of(splitter, splitter2)); ImmutableCollection splits = pipeline.split(baseSplit); assertThat(splitter.splitCallCount).isEqualTo(1); assertThat(splitter2.splitCallCount).isEqualTo(2); 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 213fe01d..a646364a 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 @@ -16,6 +16,8 @@ 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.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMaxSdkVersion; import static com.android.tools.build.bundletool.testing.TargetingUtils.lPlusVariantTargeting; @@ -23,16 +25,14 @@ import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries; import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.utils.Versions.ANDROID_P_API_VERSION; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Files.NativeLibraries; import com.android.bundle.Targeting.VariantTargeting; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableCollection; import org.junit.Test; diff --git a/src/test/java/com/android/tools/build/bundletool/testing/AppBundleBuilder.java b/src/test/java/com/android/tools/build/bundletool/testing/AppBundleBuilder.java index 71c6388c..40627450 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/AppBundleBuilder.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/AppBundleBuilder.java @@ -19,8 +19,7 @@ import com.android.tools.build.bundletool.model.AppBundle; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; -import com.android.tools.build.bundletool.utils.files.BufferedIo; -import com.google.common.collect.ImmutableCollection; +import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; @@ -38,7 +37,7 @@ public class AppBundleBuilder { static final BundleConfig DEFAULT_BUNDLE_CONFIG = BundleConfigBuilder.create().build(); - private final ImmutableCollection.Builder modules = ImmutableList.builder(); + private final ImmutableList.Builder modules = ImmutableList.builder(); private final BundleMetadata.Builder metadata = BundleMetadata.builder(); @@ -53,7 +52,7 @@ public AppBundleBuilder addModule(String name, Consumer ope } public AppBundleBuilder addMetadataFile(String namespacedDir, String fileName, Path file) { - metadata.addFile(namespacedDir, fileName, () -> BufferedIo.inputStream(file)); + metadata.addFile(namespacedDir, fileName, BufferedIo.inputStreamSupplier(file)); return this; } diff --git a/src/test/java/com/android/tools/build/bundletool/testing/AppBundleFactory.java b/src/test/java/com/android/tools/build/bundletool/testing/AppBundleFactory.java index d990be0c..76da0fbf 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/AppBundleFactory.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/AppBundleFactory.java @@ -16,20 +16,19 @@ package com.android.tools.build.bundletool.testing; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.HDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.LDPI_VALUE; 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.withMaxSdkVersion; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMinSdkVersion; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.HDPI; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.LDPI; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.createResourceTable; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.fileReference; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries; import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.tools.build.bundletool.model.AppBundle; +import com.google.common.collect.ImmutableMap; import java.io.IOException; /** A collection of convenience creators of different types of {@link AppBundle} used in testing. */ @@ -44,10 +43,16 @@ public static AppBundle createLdpiHdpiAppBundle() throws IOException { .addFile("res/drawable-ldpi/image.jpg") .addFile("res/drawable-hdpi/image.jpg") .setResourceTable( - createResourceTable( - "image", - fileReference("res/drawable-ldpi/image.jpg", LDPI), - fileReference("res/drawable-hdpi/image.jpg", HDPI))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResourceForMultipleDensities( + "image", + ImmutableMap.of( + LDPI_VALUE, + "res/drawable-ldpi/image.jpg", + HDPI_VALUE, + "res/drawable-hdpi/image.jpg")) + .build()) .setManifest(androidManifest("com.test.app"))) .build(); } diff --git a/src/test/java/com/android/tools/build/bundletool/testing/BundleConfigBuilder.java b/src/test/java/com/android/tools/build/bundletool/testing/BundleConfigBuilder.java index 4c898a0b..3fa9859e 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/BundleConfigBuilder.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/BundleConfigBuilder.java @@ -19,7 +19,7 @@ import com.android.bundle.Config.BundleConfig; import com.android.bundle.Config.Bundletool; import com.android.bundle.Config.SplitDimension; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; /** Helper to create {@link BundleConfig} instances in tests. */ public class BundleConfigBuilder { @@ -67,6 +67,11 @@ public BundleConfigBuilder addUncompressedGlob(String uncompressedGlob) { return this; } + public BundleConfigBuilder addResourcePinnedToMasterSplit(int resourceId) { + builder.getMasterResourcesBuilder().addResourceIds(resourceId); + return this; + } + public BundleConfigBuilder clearCompression() { builder.clearCompression(); return this; diff --git a/src/test/java/com/android/tools/build/bundletool/testing/BundleModuleBuilder.java b/src/test/java/com/android/tools/build/bundletool/testing/BundleModuleBuilder.java index 0f3cf779..8cd180a6 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/BundleModuleBuilder.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/BundleModuleBuilder.java @@ -28,8 +28,8 @@ import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.InMemoryModuleEntry; import com.android.tools.build.bundletool.model.ModuleEntry; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNodeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNodeBuilder; import com.google.common.collect.ImmutableSet; import java.io.IOException; @@ -95,15 +95,20 @@ public BundleModuleBuilder setManifest(XmlNode androidManifest) { public BundleModule build() throws IOException { if (androidManifest != null) { + XmlProtoNodeBuilder manifestBuilder = new XmlProtoNode(androidManifest).toBuilder(); boolean hasCode = entries.build().stream().anyMatch(entry -> entry.getPath().toString().endsWith(".dex")); if (hasCode) { - XmlProtoNodeBuilder manifestBuilder = new XmlProtoNode(androidManifest).toBuilder(); ManifestProtoUtils.clearHasCode().accept(manifestBuilder.getElement()); - androidManifest = manifestBuilder.build().getProto(); } - addFile("manifest/AndroidManifest.xml", androidManifest.toByteArray()); + // Set the 'split' attribute if one is not already set. + if (!moduleName.getName().equals(BundleModuleName.BASE_MODULE_NAME) + && !manifestBuilder.getElement().getAttribute("split").isPresent()) { + ManifestProtoUtils.withSplitId(moduleName.getName()).accept(manifestBuilder.getElement()); + } + + addFile("manifest/AndroidManifest.xml", manifestBuilder.build().getProto().toByteArray()); } return BundleModule.builder() diff --git a/src/test/java/com/android/tools/build/bundletool/testing/DeviceFactory.java b/src/test/java/com/android/tools/build/bundletool/testing/DeviceFactory.java index b035671a..b0dae24b 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/DeviceFactory.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/DeviceFactory.java @@ -16,13 +16,13 @@ package com.android.tools.build.bundletool.testing; -import static com.android.tools.build.bundletool.utils.ProtoUtils.mergeFromProtos; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; +import static com.android.tools.build.bundletool.model.utils.ProtoUtils.mergeFromProtos; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.DENSITY_ALIAS_TO_DPI_MAP; import static java.nio.charset.StandardCharsets.UTF_8; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; -import com.android.tools.build.bundletool.utils.Versions; +import com.android.tools.build.bundletool.model.utils.Versions; import com.google.protobuf.util.JsonFormat; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/test/java/com/android/tools/build/bundletool/testing/FakeAndroidHomeVariableProvider.java b/src/test/java/com/android/tools/build/bundletool/testing/FakeAndroidHomeVariableProvider.java deleted file mode 100755 index 2cabc894..00000000 --- a/src/test/java/com/android/tools/build/bundletool/testing/FakeAndroidHomeVariableProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2018 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.testing; - -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import java.util.Optional; - -/** Fake provider providing a custom value for the ANDROID_HOME environment variable for tests. */ -public class FakeAndroidHomeVariableProvider implements EnvironmentVariableProvider { - - private final String valueForAndroidHome; - - public FakeAndroidHomeVariableProvider(String valueForAndroidHome) { - this.valueForAndroidHome = valueForAndroidHome; - } - - @Override - public Optional getVariable(String name) { - if (name.equals("ANDROID_HOME")) { - return Optional.of(valueForAndroidHome); - } - return Optional.empty(); - } -} diff --git a/src/test/java/com/android/tools/build/bundletool/testing/FakeAndroidSerialVariableProvider.java b/src/test/java/com/android/tools/build/bundletool/testing/FakeAndroidSerialVariableProvider.java deleted file mode 100755 index 34ecc004..00000000 --- a/src/test/java/com/android/tools/build/bundletool/testing/FakeAndroidSerialVariableProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2018 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.testing; - -import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider; -import java.util.Optional; - -/** Fake provider providing a custom value for the ANDROID_SERIAL environment variable for tests. */ -public class FakeAndroidSerialVariableProvider implements EnvironmentVariableProvider { - - private final String valueForAndroidSerial; - - public FakeAndroidSerialVariableProvider(String valueForAndroidSerial) { - this.valueForAndroidSerial = valueForAndroidSerial; - } - - @Override - public Optional getVariable(String name) { - if (name.equals("ANDROID_SERIAL")) { - return Optional.of(valueForAndroidSerial); - } - return Optional.empty(); - } -} diff --git a/src/test/java/com/android/tools/build/bundletool/testing/FakeDevice.java b/src/test/java/com/android/tools/build/bundletool/testing/FakeDevice.java index b9cf0d28..97ca1ce6 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/FakeDevice.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/FakeDevice.java @@ -29,7 +29,7 @@ import com.android.ddmlib.TimeoutException; import com.android.sdklib.AndroidVersion; import com.android.tools.build.bundletool.device.Device; -import com.android.tools.build.bundletool.utils.Versions; +import com.android.tools.build.bundletool.model.utils.Versions; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; diff --git a/src/test/java/com/android/tools/build/bundletool/testing/FakeSystemEnvironmentProvider.java b/src/test/java/com/android/tools/build/bundletool/testing/FakeSystemEnvironmentProvider.java new file mode 100755 index 00000000..b70da185 --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/testing/FakeSystemEnvironmentProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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.testing; + +import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; +import com.google.common.collect.ImmutableMap; +import java.util.Optional; + +/** Fake implementation of {@link SystemEnvironmentProvider}. */ +public class FakeSystemEnvironmentProvider implements SystemEnvironmentProvider { + + private final ImmutableMap variables; + private final ImmutableMap properties; + + public static final String ANDROID_HOME = "ANDROID_HOME"; + public static final String ANDROID_SERIAL = "ANDROID_SERIAL"; + + /** + * Creates {@link FakeSystemEnvironmentProvider} instance. + * + * @param variables mapping between fake environment variable names and their values. + * @param properties mapping between fake system properties and their values. + */ + public FakeSystemEnvironmentProvider( + ImmutableMap variables, ImmutableMap properties) { + this.variables = variables; + this.properties = properties; + } + + /** Convenience constructor with empty mapping of system properties. */ + public FakeSystemEnvironmentProvider(ImmutableMap variables) { + this(variables, ImmutableMap.of()); + } + + @Override + public Optional getVariable(String name) { + return Optional.ofNullable(variables.get(name)); + } + + @Override + public Optional getProperty(String name) { + return Optional.ofNullable(properties.get(name)); + } +} 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 b95f0f60..a86d5fbe 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 @@ -53,10 +53,10 @@ import com.android.aapt.Resources.XmlNamespace; import com.android.aapt.Resources.XmlNode; import com.android.tools.build.bundletool.model.AndroidManifest; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNodeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNodeBuilder; import com.google.common.collect.ImmutableList; import com.google.common.collect.ObjectArrays; import java.util.Arrays; @@ -348,6 +348,15 @@ public static ManifestMutator withOnDemandAttribute(boolean value) { .setValueAsBoolean(value); } + /** Adds the type attribute under the dist:module tag. */ + public static ManifestMutator withTypeAttribute(String value) { + return manifestElement -> + manifestElement + .getOrCreateChildElement(DISTRIBUTION_NAMESPACE_URI, "module") + .getOrCreateAttribute(DISTRIBUTION_NAMESPACE_URI, "type") + .setValueAsString(value); + } + /** Same as {@link #withOnDemandAttribute(boolean)} but with the attribute not namespaced. */ public static ManifestMutator withLegacyOnDemand(boolean value) { return manifestElement -> diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtilsTest.java b/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtilsTest.java index 4bb8a9dc..e9a335d4 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtilsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtilsTest.java @@ -33,7 +33,7 @@ import com.android.aapt.Resources.XmlElement; import com.android.tools.build.bundletool.model.AndroidManifest; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.junit.Test; diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ModuleSplitUtils.java b/src/test/java/com/android/tools/build/bundletool/testing/ModuleSplitUtils.java index c686a223..ee2d3544 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ModuleSplitUtils.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ModuleSplitUtils.java @@ -24,8 +24,8 @@ import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ManifestMutator; import com.android.tools.build.bundletool.model.ModuleSplit; -import com.android.tools.build.bundletool.version.BundleToolVersion; -import com.android.tools.build.bundletool.version.Version; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableList; /** Utility to create and manipulate the {@link ModuleSplit}. */ diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ResourceTableBuilder.java b/src/test/java/com/android/tools/build/bundletool/testing/ResourceTableBuilder.java index 56a752b8..b830e8c9 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ResourceTableBuilder.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ResourceTableBuilder.java @@ -32,6 +32,8 @@ import com.android.aapt.Resources.Type; import com.android.aapt.Resources.TypeId; import com.android.aapt.Resources.Value; +import com.android.aapt.Resources.Visibility; +import com.android.aapt.Resources.Visibility.Level; import com.google.common.io.Files; import java.util.Arrays; import java.util.LinkedHashMap; @@ -184,6 +186,7 @@ public ResourceTableBuilder addResource( type.addEntry( Entry.newBuilder() .setEntryId(EntryId.newBuilder().setId(entryId)) + .setVisibility(Visibility.newBuilder().setLevel(Level.PUBLIC)) .setName(resourceName) .addAllConfigValue(Arrays.asList(configValues))); diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ResourcesTableFactory.java b/src/test/java/com/android/tools/build/bundletool/testing/ResourcesTableFactory.java index fd99781f..2c4ea8e2 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ResourcesTableFactory.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ResourcesTableFactory.java @@ -16,16 +16,16 @@ package com.android.tools.build.bundletool.testing; -import static com.android.tools.build.bundletool.utils.ProtoUtils.mergeFromProtos; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.ANY_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.HDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.LDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.MDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.NONE_DENSITY_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.TVDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.XHDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.XXHDPI_VALUE; -import static com.android.tools.build.bundletool.utils.ResourcesUtils.XXXHDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ProtoUtils.mergeFromProtos; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.ANY_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.HDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.LDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.MDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.NONE_DENSITY_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.TVDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XHDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XXHDPI_VALUE; +import static com.android.tools.build.bundletool.model.utils.ResourcesUtils.XXXHDPI_VALUE; import static java.util.Arrays.asList; import com.android.aapt.ConfigurationOuterClass.Configuration; @@ -72,7 +72,7 @@ public final class ResourcesTableFactory { // package id space is reserved for Android Framework from 0x00 till 0x7e. public static final int USER_PACKAGE_OFFSET = 0x7f; - public static final int TEST_LABEL_RESOURCE_ID = 0x7f010001; + public static final int TEST_LABEL_RESOURCE_ID = 0x7f010000; public static Configuration locale(String locale) { return Configuration.newBuilder().setLocale(locale).build(); @@ -157,41 +157,20 @@ public static Entry entry(int id, String entryName, ConfigValue... values) { .build(); } - public static ResourceTable createResourceTable() { - return resourceTable(pkg(USER_PACKAGE_OFFSET, "com.test.app")); - } - - public static ResourceTable createResourceTable(String entryName, ConfigValue... values) { + public static ResourceTable createResourceTable( + int entryId, String entryName, ConfigValue... values) { return resourceTable( pkg( USER_PACKAGE_OFFSET, "com.test.app", - type(0x01, "drawable", entry(0x01, entryName, values)))); - } - - public static ResourceTable createResourceTableWithRaw( - String entryName, ConfigValue value, String rawEntryName, ConfigValue rawValue) { - return resourceTable( - pkg( - USER_PACKAGE_OFFSET, - "com.test.app", - type(0x01, "drawable", entry(0x01, entryName, value)), - type(0x02, "raw", entry(0x01, rawEntryName, rawValue)))); - } - - public static Package packageWithTestLabel(String value, int pkgId, Configuration config) { - return pkg( - pkgId, - "com.test.app", - type(0x01, "string", entry(0x01, "test_label", value("Test feature", config)))); - } - - public static Package packageWithTestLabel(String value, int pkgId) { - return packageWithTestLabel(value, pkgId, Configuration.getDefaultInstance()); + type(0x01, "drawable", entry(entryId, entryName, values)))); } public static ResourceTable resourceTableWithTestLabel(Configuration config, String value) { - return resourceTable(packageWithTestLabel(value, USER_PACKAGE_OFFSET, config)); + return new ResourceTableBuilder() + .addPackage("com.test.app") + .addResource("string", "test_label", value(value, config)) + .build(); } public static ResourceTable resourceTableWithTestLabel(String value) { diff --git a/src/test/java/com/android/tools/build/bundletool/testing/TargetingUtils.java b/src/test/java/com/android/tools/build/bundletool/testing/TargetingUtils.java index 9417e4d4..675b402d 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/TargetingUtils.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/TargetingUtils.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.testing; -import static com.android.tools.build.bundletool.utils.ProtoUtils.mergeFromProtos; +import static com.android.tools.build.bundletool.model.utils.ProtoUtils.mergeFromProtos; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; @@ -51,11 +51,12 @@ import com.android.bundle.Targeting.TextureCompressionFormat; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; import com.android.bundle.Targeting.TextureCompressionFormatTargeting; +import com.android.bundle.Targeting.UserCountriesTargeting; import com.android.bundle.Targeting.VariantTargeting; import com.android.bundle.Targeting.VulkanVersion; import com.android.tools.build.bundletool.model.AbiName; import com.android.tools.build.bundletool.model.ModuleSplit; -import com.android.tools.build.bundletool.utils.Versions; +import com.android.tools.build.bundletool.model.utils.Versions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -162,6 +163,13 @@ public static TargetedApexImage targetedApexImage(String path, ApexImageTargetin return TargetedApexImage.newBuilder().setPath(path).setTargeting(targeting).build(); } + /** Builds APEX targeted image from the image file path and its multi-Abi targeting. */ + public static TargetedApexImage targetedApexImage(String path, MultiAbiTargeting targeting) { + ApexImageTargeting apexImageTargeting = + ApexImageTargeting.newBuilder().setMultiAbi(targeting).build(); + return TargetedApexImage.newBuilder().setPath(path).setTargeting(apexImageTargeting).build(); + } + /** Builds APEX image targeting (no alternatives) according to the Abi names. */ public static ApexImageTargeting apexImageTargeting(String... architectures) { MultiAbi.Builder multiAbi = MultiAbi.newBuilder(); @@ -476,6 +484,23 @@ public static ModuleTargeting moduleMinSdkVersionTargeting(int minSdkVersion) { .build(); } + public static ModuleTargeting moduleExcludeCountriesTargeting(String... countries) { + return moduleCountriesTargeting(true, countries); + } + + public static ModuleTargeting moduleIncludeCountriesTargeting(String... countries) { + return moduleCountriesTargeting(false, countries); + } + + public static ModuleTargeting moduleCountriesTargeting(boolean exclude, String... countries) { + return ModuleTargeting.newBuilder() + .setUserCountriesTargeting( + UserCountriesTargeting.newBuilder() + .addAllCountryCodes(Arrays.asList(countries)) + .setExclude(exclude)) + .build(); + } + public static ModuleTargeting mergeModuleTargeting( ModuleTargeting targeting, ModuleTargeting... targetings) { return mergeFromProtos(targeting, targetings); diff --git a/src/test/java/com/android/tools/build/bundletool/testing/TestUtils.java b/src/test/java/com/android/tools/build/bundletool/testing/TestUtils.java index b636cec7..22978a05 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/TestUtils.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/TestUtils.java @@ -20,10 +20,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.android.tools.build.bundletool.flags.Flag.RequiredFlagNotSetException; +import com.android.tools.build.bundletool.model.InputStreamSupplier; import com.android.tools.build.bundletool.model.ModuleEntry; import com.android.tools.build.bundletool.model.ZipPath; -import com.android.tools.build.bundletool.utils.flags.Flag.RequiredFlagNotSetException; import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.junit.jupiter.api.function.Executable; @@ -56,8 +61,7 @@ public static void expectMissingRequiredFlagException(String flag, Executable ru * instances, preserving the order. */ public static ImmutableList extractPaths(ImmutableList entries) { - return entries - .stream() + return entries.stream() .map(ModuleEntry::getPath) .map(ZipPath::toString) .collect(toImmutableList()); @@ -65,10 +69,21 @@ public static ImmutableList extractPaths(ImmutableList entr /** Extracts paths of all files having the given path prefix. */ public static ImmutableList filesUnderPath(ZipFile zipFile, ZipPath pathPrefix) { - return zipFile - .stream() + return zipFile.stream() .map(ZipEntry::getName) .filter(entryName -> ZipPath.create(entryName).startsWith(pathPrefix)) .collect(toImmutableList()); } + + public static byte[] getEntryContent(ModuleEntry entry) { + return toByteArray(entry.getContentSupplier()); + } + + public static byte[] toByteArray(InputStreamSupplier inputStreamSupplier) { + try (InputStream entryContent = inputStreamSupplier.get()) { + return ByteStreams.toByteArray(entryContent); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/src/test/java/com/android/tools/build/bundletool/validation/AbiParityValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/AbiParityValidatorTest.java index c000ac86..efbf3a31 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/AbiParityValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/AbiParityValidatorTest.java @@ -20,8 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableList; import org.junit.Test; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/AndroidManifestValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/AndroidManifestValidatorTest.java index f77bcded..3fe3203a 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/AndroidManifestValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/AndroidManifestValidatorTest.java @@ -17,6 +17,7 @@ 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.ManifestProtoUtils.androidManifestForFeature; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFeatureCondition; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFusingAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withInstallTimeDelivery; @@ -31,17 +32,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.ValidationException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestDuplicateAttributeException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MaxSdkInvalidException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MaxSdkLessThanMinInstantSdk; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MinSdkGreaterThanMaxSdkException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestSdkTargetingException.MinSdkInvalidException; -import com.android.tools.build.bundletool.exceptions.manifest.ManifestVersionCodeConflictException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestDuplicateAttributeException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MaxSdkInvalidException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MaxSdkLessThanMinInstantSdk; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MinSdkGreaterThanMaxSdkException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestSdkTargetingException.MinSdkInvalidException; +import com.android.tools.build.bundletool.model.exceptions.manifest.ManifestVersionCodeConflictException; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.android.tools.build.bundletool.testing.ManifestProtoUtils.ManifestMutator; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; import com.google.common.collect.ImmutableList; import java.io.IOException; import org.junit.Test; @@ -546,12 +547,12 @@ public void instantFeature_noMaxSdk() throws Exception { @Test public void withMultipleDistinctSplitIds_throws() throws Exception { BundleModule module = - new BundleModuleBuilder(BASE_MODULE_NAME) + new BundleModuleBuilder(FEATURE_MODULE_NAME) .setManifest( - androidManifest( + androidManifestForFeature( PKG_NAME, - withSplitId("module-split-name"), - withSecondSplitId("module-split-name2"))) + withSplitId(FEATURE_MODULE_NAME), + withSecondSplitId("modulesplitname2"))) .build(); ManifestDuplicateAttributeException e = @@ -562,16 +563,15 @@ public void withMultipleDistinctSplitIds_throws() throws Exception { assertThat(e).hasMessageThat().contains("attribute 'split' cannot be declared more than once"); } - @Test public void withMultipleEqualSplitIds() throws Exception { BundleModule module = - new BundleModuleBuilder(BASE_MODULE_NAME) + new BundleModuleBuilder(FEATURE_MODULE_NAME) .setManifest( - androidManifest( + androidManifestForFeature( PKG_NAME, - withSplitId("module-split-name"), - withSecondSplitId("module-split-name"))) + withSplitId(FEATURE_MODULE_NAME), + withSecondSplitId(FEATURE_MODULE_NAME))) .build(); // We accept multiple identical split IDs, so this should not throw. @@ -589,8 +589,8 @@ public void withoutSplitIds() throws Exception { @Test public void withOneSplitId() throws Exception { BundleModule module = - new BundleModuleBuilder(BASE_MODULE_NAME) - .setManifest(androidManifest(PKG_NAME, withSplitId("module-split-name"))) + new BundleModuleBuilder(FEATURE_MODULE_NAME) + .setManifest(androidManifestForFeature(PKG_NAME, withSplitId(FEATURE_MODULE_NAME))) .build(); new AndroidManifestValidator().validateModule(module); diff --git a/src/test/java/com/android/tools/build/bundletool/validation/ApexBundleValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/ApexBundleValidatorTest.java index 29164f16..27976161 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/ApexBundleValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/ApexBundleValidatorTest.java @@ -22,8 +22,8 @@ import com.android.bundle.Files.ApexImages; import com.android.bundle.Files.TargetedApexImage; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -90,7 +90,10 @@ public void validateModule_untargetedImageFile_throws() throws Exception { .setManifest(androidManifest(PKG_NAME)) .setApexConfig(APEX_CONFIG) .addFile("root/apex_manifest.json") + .addFile("apex/x86_64.img") .addFile("apex/x86.img") + .addFile("apex/armeabi-v7a.img") + .addFile("apex/arm64-v8a.img") .addFile("apex/x86_64.x86.img") .build(); @@ -103,12 +106,21 @@ public void validateModule_untargetedImageFile_throws() throws Exception { @Test public void validateModule_missingTargetedImageFile_throws() throws Exception { + ApexImages apexConfig = + APEX_CONFIG + .toBuilder() + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.x86.img")) + .build(); BundleModule apexModule = new BundleModuleBuilder("apexTestModule") .setManifest(androidManifest(PKG_NAME)) - .setApexConfig(APEX_CONFIG) + .setApexConfig(apexConfig) .addFile("root/apex_manifest.json") - // No image files under apex/. + .addFile("apex/x86_64.img") + .addFile("apex/x86.img") + .addFile("apex/armeabi-v7a.img") + .addFile("apex/arm64-v8a.img") + // x86_64.x86 missing. .build(); ValidationException exception = @@ -147,6 +159,62 @@ public void validateModule_imageFilesTargetSameSetOfAbis_throws() throws Excepti @Test public void validateModule_singletonAbiMissing_throws() throws Exception { + ApexImages apexConfig = + ApexImages.newBuilder() + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/arm64-v8a.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.x86.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.armeabi-v7a.img")) + .build(); + BundleModule apexModule = + new BundleModuleBuilder("apexTestModule") + .setManifest(androidManifest(PKG_NAME)) + .setApexConfig(apexConfig) + .addFile("root/apex_manifest.json") + .addFile("apex/x86_64.img") + .addFile("apex/x86.img") + .addFile("apex/arm64-v8a.img") + // armeabi-v7a is missing. + .addFile("apex/x86_64.x86.img") + .addFile("apex/x86_64.armeabi-v7a.img") + .build(); + + ValidationException exception = + assertThrows( + ValidationException.class, () -> new ApexBundleValidator().validateModule(apexModule)); + + assertThat(exception).hasMessageThat().contains("APEX bundle must contain one of"); + } + + @Test + public void validateModule_singleton64BitAbiMissing_doesNotThrow() throws Exception { + ApexImages apexConfig = + ApexImages.newBuilder() + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/armeabi-v7a.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.x86.img")) + .addImage(TargetedApexImage.newBuilder().setPath("apex/arm64-v8a.armeabi-v7a.img")) + .build(); + BundleModule apexModule = + new BundleModuleBuilder("apexTestModule") + .setManifest(androidManifest(PKG_NAME)) + .setApexConfig(apexConfig) + .addFile("root/apex_manifest.json") + .addFile("apex/x86_64.img") + .addFile("apex/x86.img") + .addFile("apex/armeabi-v7a.img") + // arm64-v8a is missing (but arm64-v8a.armeabi-v7a is present). + .addFile("apex/x86_64.x86.img") + .addFile("apex/arm64-v8a.armeabi-v7a.img") + .build(); + + new ApexBundleValidator().validateModule(apexModule); + } + + @Test + public void validateModule_singleton64BitAbiAnd64With32Missing_throws() throws Exception { ApexImages apexConfig = ApexImages.newBuilder() .addImage(TargetedApexImage.newBuilder().setPath("apex/x86_64.img")) @@ -163,7 +231,7 @@ public void validateModule_singletonAbiMissing_throws() throws Exception { .addFile("apex/x86_64.img") .addFile("apex/x86.img") .addFile("apex/armeabi-v7a.img") - // arm64-v8a.img missing. + // Both arm64-v8a and arm64-v8a.armeabi-v7a are missing. .addFile("apex/x86_64.x86.img") .addFile("apex/x86_64.armeabi-v7a.img") .build(); @@ -172,9 +240,7 @@ public void validateModule_singletonAbiMissing_throws() throws Exception { assertThrows( ValidationException.class, () -> new ApexBundleValidator().validateModule(apexModule)); - assertThat(exception) - .hasMessageThat() - .contains("APEX bundle must contain all these singleton architectures"); + assertThat(exception).hasMessageThat().contains("APEX bundle must contain one of"); } @Test diff --git a/src/test/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidatorTest.java index 86d6d9e1..f5dec585 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/AssetsTargetingValidatorTest.java @@ -31,8 +31,8 @@ import com.android.bundle.Files.Assets; import com.android.bundle.Targeting.AssetsDirectoryTargeting; import com.android.bundle.Targeting.TextureCompressionFormat.TextureCompressionFormatAlias; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/BundleConfigValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/BundleConfigValidatorTest.java index 8fa631b0..98cd9ac3 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/BundleConfigValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/BundleConfigValidatorTest.java @@ -19,13 +19,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.android.aapt.Resources.ResourceTable; import com.android.bundle.Config.BundleConfig; import com.android.bundle.Config.SplitDimension; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.AppBundle; +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.version.BundleToolVersion; import com.android.tools.build.bundletool.testing.AppBundleBuilder; import com.android.tools.build.bundletool.testing.BundleConfigBuilder; -import com.android.tools.build.bundletool.version.BundleToolVersion; +import com.android.tools.build.bundletool.testing.ResourceTableBuilder; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; @@ -186,14 +189,81 @@ public void version_invalid_throws() throws Exception { assertThat(e).hasMessageThat().contains("Invalid version"); } + @Test + public void masterResources_valid_ok() throws Exception { + ResourceTable resourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app") + .addStringResource("label", "Hello World") + .build(); + + int resourceId = + ResourcesUtils.entries(resourceTable).findFirst().get().getResourceId().getFullResourceId(); + + AppBundle appBundle = + createAppBundleBuilder( + BundleConfigBuilder.create().addResourcePinnedToMasterSplit(resourceId)) + .addModule( + "feature", + module -> + module + .setResourceTable(resourceTable) + .setManifest(androidManifest("com.test.app"))) + .build(); + + new BundleConfigValidator().validateBundle(appBundle); + } + + @Test + public void masterResources_undefinedResourceId_throws() throws Exception { + ResourceTable resourceTable = + new ResourceTableBuilder() + .addPackage("com.test.app") + .addStringResource("label", "Hello World") + .build(); + + int nonExistentResourceId = + ResourcesUtils.entries(resourceTable).findFirst().get().getResourceId().getFullResourceId() + + 1; + + AppBundle appBundle = + createAppBundleBuilder( + BundleConfigBuilder.create().addResourcePinnedToMasterSplit(nonExistentResourceId)) + .addModule( + "feature", + module -> + module + .setResourceTable(resourceTable) + .setManifest(androidManifest("com.test.app"))) + .build(); + + ValidationException e = + assertThrows( + ValidationException.class, () -> new BundleConfigValidator().validateBundle(appBundle)); + assertThat(e) + .hasMessageThat() + .contains( + "The Master Resources list contains resource IDs not defined in any module. " + + "For example: 0x7f010001"); + } + private static AppBundle createAppBundle(BundleConfigBuilder bundleConfig) throws IOException { return createAppBundle(bundleConfig.build()); } private static AppBundle createAppBundle(BundleConfig bundleConfig) throws IOException { + return createAppBundleBuilder(bundleConfig).build(); + } + + private static AppBundleBuilder createAppBundleBuilder(BundleConfigBuilder bundleConfig) + throws IOException { + return createAppBundleBuilder(bundleConfig.build()); + } + + private static AppBundleBuilder createAppBundleBuilder(BundleConfig bundleConfig) + throws IOException { return new AppBundleBuilder() .addModule("base", module -> module.setManifest(androidManifest("com.app"))) - .setBundleConfig(bundleConfig) - .build(); + .setBundleConfig(bundleConfig); } } diff --git a/src/test/java/com/android/tools/build/bundletool/validation/BundleFilesValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/BundleFilesValidatorTest.java index fd9fb45c..6faa0917 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/BundleFilesValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/BundleFilesValidatorTest.java @@ -20,16 +20,16 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.FileUsesReservedNameException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.FilesInResourceDirectoryRootException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidApexImagePathException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidFileExtensionInDirectoryException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidFileNameInDirectoryException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidNativeArchitectureNameException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.InvalidNativeLibraryPathException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.UnknownFileOrDirectoryFoundInModuleException; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.FileUsesReservedNameException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.FilesInResourceDirectoryRootException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidApexImagePathException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidFileExtensionInDirectoryException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidFileNameInDirectoryException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidNativeArchitectureNameException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.InvalidNativeLibraryPathException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.UnknownFileOrDirectoryFoundInModuleException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/BundleZipValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/BundleZipValidatorTest.java index 11e39718..9a04464c 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/BundleZipValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/BundleZipValidatorTest.java @@ -19,9 +19,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/DexFilesValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/DexFilesValidatorTest.java index 40ca5355..611219e9 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/DexFilesValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/DexFilesValidatorTest.java @@ -22,8 +22,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/EntryClashValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/EntryClashValidatorTest.java index f1919726..812c3326 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/EntryClashValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/EntryClashValidatorTest.java @@ -29,8 +29,8 @@ import com.android.bundle.Files.NativeLibraries; import com.android.bundle.Files.TargetedAssetsDirectory; import com.android.bundle.Files.TargetedNativeDirectory; -import com.android.tools.build.bundletool.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableList; import org.junit.Ignore; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidatorTest.java index 3214be06..54b65d39 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/MandatoryFilesPresenceValidatorTest.java @@ -19,10 +19,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.MandatoryBundleFileMissingException; -import com.android.tools.build.bundletool.exceptions.BundleFileTypesException.MandatoryModuleFileMissingException; import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.MandatoryBundleFileMissingException; +import com.android.tools.build.bundletool.model.exceptions.BundleFileTypesException.MandatoryModuleFileMissingException; import java.nio.file.Path; import java.util.zip.ZipFile; import org.junit.Before; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidatorTest.java index 3150b6a9..03197778 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidatorTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.aapt.Resources.XmlNode; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -155,36 +155,6 @@ public void validateAllModules_cycle_throws() throws Exception { assertThat(exception).hasMessageThat().contains("Found cyclic dependency between modules"); } - @Test - public void validateAllModules_baseDeclaresSplitId_throws() throws Exception { - ImmutableList allModules = - ImmutableList.of(module("base", androidManifest(PKG_NAME, withSplitId("base")))); - - ValidationException exception = - assertThrows( - ValidationException.class, - () -> new ModuleDependencyValidator().validateAllModules(allModules)); - - assertThat(exception).hasMessageThat().contains("should not declare split ID"); - } - - @Test - public void validateAllModules_splitIdDifferentFromModuleName_throws() throws Exception { - ImmutableList allModules = - ImmutableList.of( - module("base", androidManifest(PKG_NAME)), - module("feature", androidManifest(PKG_NAME, withSplitId("not_feature")))); - - ValidationException exception = - assertThrows( - ValidationException.class, - () -> new ModuleDependencyValidator().validateAllModules(allModules)); - - assertThat(exception) - .hasMessageThat() - .contains("Module 'feature' declares in its manifest that the split ID is 'not_feature'"); - } - private BundleModule module(String moduleName, XmlNode manifest) throws IOException { return new BundleModuleBuilder(moduleName).setManifest(manifest).build(); } diff --git a/src/test/java/com/android/tools/build/bundletool/validation/ModuleNamesValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/ModuleNamesValidatorTest.java new file mode 100755 index 00000000..9b5650fd --- /dev/null +++ b/src/test/java/com/android/tools/build/bundletool/validation/ModuleNamesValidatorTest.java @@ -0,0 +1,150 @@ +/* + * 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.validation; + +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifestForFeature; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withSplitId; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.android.bundle.Config.BundleConfig; +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.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ModuleNamesValidatorTest { + + @Test + public void allModuleValid() { + BundleModule base = + buildBundleModule("base").setAndroidManifestProto(androidManifest("com.app")).build(); + BundleModule feature1 = + buildBundleModule("feature1") + .setAndroidManifestProto(androidManifestForFeature("com.app", withSplitId("feature1"))) + .build(); + BundleModule feature2 = + buildBundleModule("feature2") + .setAndroidManifestProto(androidManifest("com.app", withSplitId("feature2"))) + .build(); + + // No exception = pass. + new ModuleNamesValidator().validateAllModules(ImmutableList.of(base, feature1, feature2)); + } + + @Test + public void moreThanOneModuleWithoutSplitId() { + BundleModule base1 = + buildBundleModule("base").setAndroidManifestProto(androidManifest("com.app")).build(); + BundleModule base2 = + buildBundleModule("base").setAndroidManifestProto(androidManifest("com.app")).build(); + + ValidationException expected = + assertThrows( + ValidationException.class, + () -> new ModuleNamesValidator().validateAllModules(ImmutableList.of(base1, base2))); + assertThat(expected) + .hasMessageThat() + .contains( + "More than one module was found without the 'split' attribute set in the " + + "AndroidManifest.xml."); + } + + @Test + public void moreThanOneSameFeatureModule() { + BundleModule base = + buildBundleModule("base").setAndroidManifestProto(androidManifest("com.app")).build(); + BundleModule feature1 = + buildBundleModule("feature") + .setAndroidManifestProto(androidManifestForFeature("com.app", withSplitId("feature"))) + .build(); + BundleModule feature2 = + buildBundleModule("feature") + .setAndroidManifestProto(androidManifest("com.app", withSplitId("feature"))) + .build(); + + ValidationException expected = + assertThrows( + ValidationException.class, + () -> + new ModuleNamesValidator() + .validateAllModules(ImmutableList.of(base, feature1, feature2))); + assertThat(expected) + .hasMessageThat() + .contains("More than one module have the 'split' attribute set to 'feature'"); + } + + @Test + public void splitIdSetOnBaseModule() { + BundleModule base = + buildBundleModule("base") + .setAndroidManifestProto(androidManifest("com.app", withSplitId("base"))) + .build(); + + ValidationException expected = + assertThrows( + ValidationException.class, + () -> new ModuleNamesValidator().validateAllModules(ImmutableList.of(base))); + assertThat(expected) + .hasMessageThat() + .contains( + "The base module should not have the 'split' attribute set in the AndroidManifest.xml"); + } + + @Test + public void splitIdDoesNotMatchModuleName() { + BundleModule base = + buildBundleModule("base").setAndroidManifestProto(androidManifest("com.app")).build(); + BundleModule feature = + buildBundleModule("module") + .setAndroidManifestProto(androidManifest("com.app", withSplitId("feature"))) + .build(); + ValidationException expected = + assertThrows( + ValidationException.class, + () -> new ModuleNamesValidator().validateAllModules(ImmutableList.of(base, feature))); + assertThat(expected) + .hasMessageThat() + .contains( + "The 'split' attribute in the AndroidManifest.xml of modules must be the name of the " + + "module, but has the value 'feature' in module 'module'"); + } + + @Test + public void noBaseModule() { + BundleModule feature = + buildBundleModule("feature") + .setAndroidManifestProto(androidManifest("com.app", withSplitId("feature"))) + .build(); + + ValidationException expected = + assertThrows( + ValidationException.class, + () -> new ModuleNamesValidator().validateAllModules(ImmutableList.of(feature))); + assertThat(expected).hasMessageThat().contains("No base module found."); + } + + private static BundleModule.Builder buildBundleModule(String moduleName) { + return BundleModule.builder() + .setName(BundleModuleName.create(moduleName)) + .setBundleConfig(BundleConfig.getDefaultInstance()); + } +} diff --git a/src/test/java/com/android/tools/build/bundletool/validation/ModuleTitleValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/ModuleTitleValidatorTest.java index 8bd759ec..70266db9 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/ModuleTitleValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/ModuleTitleValidatorTest.java @@ -28,8 +28,8 @@ import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlNode; import com.android.bundle.Config.BundleConfig; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleConfigBuilder; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.google.common.collect.ImmutableList; diff --git a/src/test/java/com/android/tools/build/bundletool/validation/NativeTargetingValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/NativeTargetingValidatorTest.java index 036862fa..02f05397 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/NativeTargetingValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/NativeTargetingValidatorTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Files.NativeLibraries; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import org.junit.Test; import org.junit.runner.RunWith; 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 d0ec7a6b..e7fda86c 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,16 +17,14 @@ 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.createResourceTable; -import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.fileReference; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.android.aapt.ConfigurationOuterClass.Configuration; import com.android.aapt.Resources.ResourceTable; -import com.android.tools.build.bundletool.exceptions.ValidationException; import com.android.tools.build.bundletool.model.BundleModule; +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 org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -51,9 +49,10 @@ public void validateModule_validWithResources_succeeds() throws Exception { new BundleModuleBuilder("module") .addFile("res/drawable/icon.png") .setResourceTable( - createResourceTable( - "icon", - fileReference("res/drawable/icon.png", Configuration.getDefaultInstance()))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResource("icon", "res/drawable/icon.png") + .build()) .setManifest(androidManifest("com.test.app")) .build(); @@ -65,9 +64,10 @@ public void validateModule_nonReferencedFile_throws() throws Exception { BundleModule module = new BundleModuleBuilder("module") .setResourceTable( - createResourceTable( - "icon", - fileReference("res/drawable/icon.png", Configuration.getDefaultInstance()))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResource("icon", "res/drawable/icon.png") + .build()) .setManifest(androidManifest("com.test.app")) .build(); @@ -123,8 +123,10 @@ public void validateModule_fileOutsideRes_throws() throws Exception { new BundleModuleBuilder("module") .addFile("assets/icon.png") .setResourceTable( - createResourceTable( - "icon", fileReference("assets/icon.png", Configuration.getDefaultInstance()))) + new ResourceTableBuilder() + .addPackage("com.test.app") + .addDrawableResource("icon", "assets/icon.png") + .build()) .setManifest(androidManifest("com.test.app")) .build(); diff --git a/src/test/java/com/android/tools/build/bundletool/validation/ValidatorRunnerTest.java b/src/test/java/com/android/tools/build/bundletool/validation/ValidatorRunnerTest.java index 8c94b012..7a04be0c 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/ValidatorRunnerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/ValidatorRunnerTest.java @@ -17,6 +17,7 @@ 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.ManifestProtoUtils.withSplitId; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static org.mockito.Matchers.anyObject; @@ -99,7 +100,7 @@ public void validateBundle_invokesRightSubValidatorMethods() throws Exception { .addFileWithContent(ZipPath.create("BundleConfig.pb"), BUNDLE_CONFIG.toByteArray()) .addFileWithProtoContent( ZipPath.create("moduleX/manifest/AndroidManifest.xml"), - androidManifest("com.test.app")) + androidManifest("com.test.app", withSplitId("moduleX"))) .addFileWithProtoContent( ZipPath.create("moduleX/assets.pb"), Assets.getDefaultInstance()) .addFileWithProtoContent( @@ -110,11 +111,12 @@ public void validateBundle_invokesRightSubValidatorMethods() throws Exception { .addFileWithContent(ZipPath.create("moduleX/lib/x86/libX.so"), DUMMY_CONTENT) .addFileWithProtoContent( ZipPath.create("moduleY/manifest/AndroidManifest.xml"), - androidManifest("com.test.app")) + androidManifest("com.test.app", withSplitId("moduleY"))) .addFileWithContent(ZipPath.create("moduleY/assets/file.txt"), DUMMY_CONTENT) .writeTo(tempFolder.resolve("bundle.aab")); AppBundle bundle = AppBundle.buildFromZip(new ZipFile(bundlePath.toFile())); - ImmutableList bundleModules = ImmutableList.copyOf(bundle.getModules().values()); + ImmutableList bundleFeatureModules = + ImmutableList.copyOf(bundle.getFeatureModules().values()); new ValidatorRunner(ImmutableList.of(validator)).validateBundle(bundle); @@ -122,12 +124,13 @@ public void validateBundle_invokesRightSubValidatorMethods() throws Exception { ArgumentCaptor fileArgs = ArgumentCaptor.forClass(ZipPath.class); verify(validator).validateBundle(eq(bundle)); - verify(validator).validateAllModules(eq(bundleModules)); + verify(validator).validateAllModules(eq(bundleFeatureModules)); verify(validator, times(2)).validateModule(moduleArgs.capture()); verify(validator, atLeastOnce()).validateModuleFile(fileArgs.capture()); verifyNoMoreInteractions(validator); - assertThat(moduleArgs.getAllValues()).containsExactlyElementsIn(bundle.getModules().values()); + assertThat(moduleArgs.getAllValues()) + .containsExactlyElementsIn(bundle.getFeatureModules().values()); assertThat(fileArgs.getAllValues().stream().map(ZipPath::toString)) // Note that special module files (AndroidManifest.xml, assets.pb, native.pb, resources.pb) // are NOT passed to the validators. @@ -141,27 +144,28 @@ public void validateBundle_invokesSubValidatorsInSequence() throws Exception { .addFileWithContent(ZipPath.create("BundleConfig.pb"), BUNDLE_CONFIG.toByteArray()) .addFileWithProtoContent( ZipPath.create("moduleX/manifest/AndroidManifest.xml"), - androidManifest("com.test.app")) + androidManifest("com.test.app", withSplitId("moduleX"))) .addFileWithContent(ZipPath.create("moduleX/assets/file.txt"), DUMMY_CONTENT) .addFileWithProtoContent( ZipPath.create("moduleY/manifest/AndroidManifest.xml"), - androidManifest("com.test.app")) + androidManifest("com.test.app", withSplitId("moduleY"))) .addFileWithContent(ZipPath.create("moduleY/assets/file.txt"), DUMMY_CONTENT) .writeTo(tempFolder.resolve("bundle.aab")); AppBundle bundle = AppBundle.buildFromZip(new ZipFile(bundlePath.toFile())); - ImmutableList bundleModules = ImmutableList.copyOf(bundle.getModules().values()); + ImmutableList bundleFeatureModules = + ImmutableList.copyOf(bundle.getFeatureModules().values()); new ValidatorRunner(ImmutableList.of(validator, validator2)).validateBundle(bundle); InOrder order = Mockito.inOrder(validator, validator2); order.verify(validator).validateBundle(eq(bundle)); - order.verify(validator).validateAllModules(eq(bundleModules)); + order.verify(validator).validateAllModules(eq(bundleFeatureModules)); order.verify(validator).validateModule(anyObject()); order.verify(validator, atLeastOnce()).validateModuleFile(anyObject()); order.verify(validator2).validateBundle(eq(bundle)); - order.verify(validator2).validateAllModules(eq(bundleModules)); + order.verify(validator2).validateAllModules(eq(bundleFeatureModules)); order.verify(validator2).validateModule(anyObject()); order.verify(validator2, atLeastOnce()).validateModuleFile(anyObject()); diff --git a/src/test/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverterTest.java b/src/test/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverterTest.java index c202dea7..02d9e239 100755 --- a/src/test/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/xml/XmlProtoToXmlConverterTest.java @@ -17,10 +17,10 @@ import static com.google.common.truth.Truth.assertThat; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoAttributeBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElement; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoElementBuilder; -import com.android.tools.build.bundletool.utils.xmlproto.XmlProtoNode; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; +import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoNode; import com.google.common.base.Joiner; import org.junit.Test; import org.junit.runner.RunWith; @@ -223,6 +223,121 @@ public void testNameOfNamespacedAttributeRemoved_doesNotCrash() { .isEqualTo(String.format("%n")); } + @Test + public void testNoNamespaceDeclaration_doesNotCrash() { + XmlProtoNode proto = + XmlProtoNode.createElementNode( + XmlProtoElementBuilder.create("root") + .addAttribute( + XmlProtoAttributeBuilder.create("http://uri", "key").setValueAsString("value")) + .addAttribute( + XmlProtoAttributeBuilder.create("http://uri", "key2") + .setValueAsString("value2")) + .addAttribute( + XmlProtoAttributeBuilder.create("http://other-uri", "other-key") + .setValueAsString("other-value")) + .build()); + + Document document = XmlProtoToXmlConverter.convert(proto); + String xmlString = XmlUtils.documentToString(document); + + assertThat(xmlString) + .isEqualTo( + String.format( + "%n")); + } + + @Test + public void testNoNamespaceDeclarationWithCommonUris() { + XmlProtoNode proto = + XmlProtoNode.createElementNode( + XmlProtoElementBuilder.create("root") + .addAttribute( + XmlProtoAttributeBuilder.create( + "http://schemas.android.com/apk/res/android", "android-key") + .setValueAsString("android-value")) + .addAttribute( + XmlProtoAttributeBuilder.create( + "http://schemas.android.com/apk/res/android", "android-key2") + .setValueAsString("android-value2")) + .addAttribute( + XmlProtoAttributeBuilder.create( + "http://schemas.android.com/apk/distribution", "dist-key") + .setValueAsString("dist-value")) + .addAttribute( + XmlProtoAttributeBuilder.create("http://schemas.android.com/tools", "tools-key") + .setValueAsString("tools-value")) + .build()); + + Document document = XmlProtoToXmlConverter.convert(proto); + String xmlString = XmlUtils.documentToString(document); + + assertThat(xmlString) + .isEqualTo( + String.format( + "%n")); + } + + @Test + public void testNoNamespaceDeclarationWithNestedElementsWithCommonUris() { + XmlProtoNode proto = + XmlProtoNode.createElementNode( + XmlProtoElementBuilder.create("root") + .addChildElement( + XmlProtoElementBuilder.create( + "http://schemas.android.com/apk/res/android", "android-element") + .addChildElement( + XmlProtoElementBuilder.create( + "http://schemas.android.com/apk/res/android", "android-element2"))) + .build()); + + Document document = XmlProtoToXmlConverter.convert(proto); + String xmlString = XmlUtils.documentToString(document); + + assertThat(xmlString) + .isEqualTo( + String.format( + "%n" + + " %n" + + " %n" + + " %n" + + "%n")); + } + + @Test + public void testCommonUriPrefixAlreadyInUse() { + XmlProtoNode proto = + XmlProtoNode.createElementNode( + XmlProtoElementBuilder.create("root") + .addNamespaceDeclaration("android", "http://uri") + .addAttribute( + XmlProtoAttributeBuilder.create( + "http://schemas.android.com/apk/res/android", "key") + .setValueAsString("value")) + .build()); + + Document document = XmlProtoToXmlConverter.convert(proto); + String xmlString = XmlUtils.documentToString(document); + + assertThat(xmlString) + .isEqualTo( + String.format( + "%n")); + } + private static XmlProtoAttributeBuilder newAttribute(String attrName) { return XmlProtoAttributeBuilder.create(attrName); }