use of com.android.bundle.Files.TargetedAssetsDirectory in project bundletool by google.
the class AssetsDimensionSplitterFactory method createSplitter.
/**
* Creates a {@link ModuleSplitSplitter} capable of splitting on a given Asset targeting
* dimension, with, optionally, a dimension to be removed from asset paths.
*
* @param <T> the proto buffer message class representing the splitting targeting dimension.
* @param dimensionGetter function that extracts the sub-message representing a targeting
* dimension.
* @param targetingSetter function that creates a split targeting that will be merged with the
* targeting of the input {@link ModuleSplit}.
* @param hasTargeting predicate to test if the input {@link ModuleSplit} is already targeting on
* the dimension of this splitter.
* @param targetingDimensionToRemove If not empty, the targeting for this dimension will be
* removed from asset paths (i.e: suffixes like #tcf_xxx will be removed from paths).
* @return {@link ModuleSplitSplitter} for a given dimension functions.
*/
public static <T extends Message> ModuleSplitSplitter createSplitter(Function<AssetsDirectoryTargeting, T> dimensionGetter, Function<T, ApkTargeting> targetingSetter, Predicate<ApkTargeting> hasTargeting, Optional<TargetingDimension> targetingDimensionToRemove) {
return new ModuleSplitSplitter() {
@Override
public ImmutableCollection<ModuleSplit> split(ModuleSplit split) {
checkArgument(!hasTargeting.test(split.getApkTargeting()), "Split is already targeting the splitting dimension.");
return split.getAssetsConfig().map(assetsConfig -> splitAssetsDirectories(assetsConfig, split)).orElse(ImmutableList.of(split)).stream().map(moduleSplit -> moduleSplit.isMasterSplit() ? moduleSplit : removeAssetsTargeting(moduleSplit)).collect(toImmutableList());
}
private ModuleSplit removeAssetsTargeting(ModuleSplit split) {
return targetingDimensionToRemove.isPresent() ? SuffixStripper.createForDimension(targetingDimensionToRemove.get()).removeAssetsTargeting(split) : split;
}
private ImmutableList<ModuleSplit> splitAssetsDirectories(Assets assets, ModuleSplit split) {
Multimap<T, TargetedAssetsDirectory> directoriesMap = Multimaps.filterKeys(Multimaps.index(assets.getDirectoryList(), targetedDirectory -> dimensionGetter.apply(targetedDirectory.getTargeting())), not(this::isDefaultTargeting));
ImmutableList.Builder<ModuleSplit> splitsBuilder = new ImmutableList.Builder<>();
// Generate config splits.
directoriesMap.asMap().entrySet().forEach(entry -> {
ImmutableList<ModuleEntry> entries = listEntriesFromDirectories(entry.getValue(), split);
if (entries.isEmpty()) {
return;
}
ModuleSplit.Builder modifiedSplit = split.toBuilder();
modifiedSplit.setEntries(entries).setApkTargeting(generateTargeting(split.getApkTargeting(), entry.getKey())).setMasterSplit(false).addMasterManifestMutator(withSplitsRequired(true));
splitsBuilder.add(modifiedSplit.build());
});
// Ensure that master split (even an empty one) always exists.
ModuleSplit defaultSplit = getDefaultAssetsSplit(split, splitsBuilder.build());
if (defaultSplit.isMasterSplit() || !defaultSplit.getEntries().isEmpty()) {
splitsBuilder.add(defaultSplit);
}
return splitsBuilder.build();
}
private ModuleSplit getDefaultAssetsSplit(ModuleSplit inputSplit, ImmutableList<ModuleSplit> configSplits) {
ImmutableSet<ModuleEntry> claimedEntries = configSplits.stream().map(ModuleSplit::getEntries).flatMap(Collection::stream).collect(toImmutableSet());
return inputSplit.toBuilder().setEntries(inputSplit.getEntries().stream().filter(not(claimedEntries::contains)).collect(toImmutableList())).build();
}
private boolean isDefaultTargeting(T splittingDimensionTargeting) {
return splittingDimensionTargeting.equals(splittingDimensionTargeting.getDefaultInstanceForType());
}
private ApkTargeting generateTargeting(ApkTargeting splitTargeting, T extraTargeting) {
if (isDefaultTargeting(extraTargeting)) {
return splitTargeting;
}
return splitTargeting.toBuilder().mergeFrom(targetingSetter.apply(extraTargeting)).build();
}
private ImmutableList<ModuleEntry> listEntriesFromDirectories(Collection<TargetedAssetsDirectory> directories, ModuleSplit moduleSplit) {
return directories.stream().map(targetedAssetsDirectory -> ZipPath.create(targetedAssetsDirectory.getPath())).flatMap(moduleSplit::getEntriesInDirectory).collect(toImmutableList());
}
};
}
use of com.android.bundle.Files.TargetedAssetsDirectory in project bundletool by google.
the class SameTargetingMergerTest method assetsConfigMerging_mergeAssetsWithIntersection.
@Test
public void assetsConfigMerging_mergeAssetsWithIntersection() throws Exception {
ModuleSplit moduleSplit = createModuleSplitBuilder().setEntries(ImmutableList.of(createModuleEntryForFile("assets/some_assets/file.txt", DUMMY_CONTENT))).setApkTargeting(ApkTargeting.getDefaultInstance()).setAssetsConfig(Assets.newBuilder().addDirectory(TargetedAssetsDirectory.newBuilder().setPath("assets/some_assets").setTargeting(createAssetsDirectoryLanguageTargeting("de")).build()).build()).build();
ModuleSplit moduleSplit2 = createModuleSplitBuilder().setEntries(ImmutableList.of(createModuleEntryForFile("assets/some_assets/file.txt", DUMMY_CONTENT), createModuleEntryForFile("assets/some_other_assets/file.txt", DUMMY_CONTENT))).setApkTargeting(ApkTargeting.getDefaultInstance()).setAssetsConfig(Assets.newBuilder().addDirectory(TargetedAssetsDirectory.newBuilder().setPath("assets/some_assets").setTargeting(createAssetsDirectoryLanguageTargeting("de")).build()).addDirectory(TargetedAssetsDirectory.newBuilder().setPath("assets/some_other_assets").build()).build()).build();
ImmutableCollection<ModuleSplit> splits = new SameTargetingMerger().merge(ImmutableList.of(moduleSplit, moduleSplit2));
assertThat(splits).hasSize(1);
ModuleSplit masterSplit = splits.iterator().next();
assertThat(masterSplit.getAssetsConfig()).isPresent();
assertThat(masterSplit.getAssetsConfig().get().getDirectoryList().stream().map(TargetedAssetsDirectory::getPath)).containsExactly("assets/some_assets", "assets/some_other_assets");
}
use of com.android.bundle.Files.TargetedAssetsDirectory in project bundletool by google.
the class ModuleSplitsToShardMergerTest method mergeSingleShard_mergeAssetsWithIntersection.
@Test
public void mergeSingleShard_mergeAssetsWithIntersection() throws Exception {
ModuleSplit baseModuleSplit = createModuleSplitBuilder().setEntries(ImmutableList.of(createModuleEntryForFile("assets/some_assets/file.txt", DUMMY_CONTENT))).setAssetsConfig(Assets.newBuilder().addDirectory(TargetedAssetsDirectory.newBuilder().setPath("assets/some_assets").setTargeting(createAssetsDirectoryLanguageTargeting("de")).build()).build()).build();
ModuleSplit featureModuleSplit = createModuleSplitBuilder().setModuleName(BundleModuleName.create(FEATURE_MODULE_NAME)).setEntries(ImmutableList.of(createModuleEntryForFile("assets/some_assets/file.txt", DUMMY_CONTENT), createModuleEntryForFile("assets/some_other_assets/file.txt", DUMMY_CONTENT))).setAssetsConfig(Assets.newBuilder().addDirectory(TargetedAssetsDirectory.newBuilder().setPath("assets/some_assets").setTargeting(createAssetsDirectoryLanguageTargeting("de")).build()).addDirectory(TargetedAssetsDirectory.newBuilder().setPath("assets/some_other_assets").build()).build()).build();
ModuleSplit merged = splitsToShardMerger.mergeSingleShard(ImmutableList.of(baseModuleSplit, featureModuleSplit), createCache());
assertThat(extractPaths(merged.getEntries())).containsExactly("assets/some_assets/file.txt", "assets/some_other_assets/file.txt");
assertThat(merged.getAssetsConfig()).isPresent();
assertThat(merged.getAssetsConfig().get().getDirectoryList().stream().map(TargetedAssetsDirectory::getPath)).containsExactly("assets/some_assets", "assets/some_other_assets");
}
use of com.android.bundle.Files.TargetedAssetsDirectory in project bundletool by google.
the class ModuleSplitsToShardMerger method mergeSingleShard.
/**
* Gets a list of splits, and merges them into a single standalone APK (aka shard).
*
* <p>Allows to customize split type {@code mergedSplitType} of merged shard and Android manifest
* merger {@code manifestMerger}.
*/
public ModuleSplit mergeSingleShard(ImmutableCollection<ModuleSplit> splitsOfShard, Map<ImmutableSet<ModuleEntry>, ImmutableList<Path>> mergedDexCache, SplitType mergedSplitType, AndroidManifestMerger manifestMerger) {
ListMultimap<BundleModuleName, ModuleEntry> dexFilesToMergeByModule = ArrayListMultimap.create();
// If multiple splits were generated from one module, we'll see the same manifest multiple
// times. The multimap filters out identical (module name, manifest) pairs by contract.
// All splits of a module should have the same manifest, so the following multimap should
// associate just one value with each key. This is checked explicitly for the base module
// because the manifest merger requires *single* base manifest.
SetMultimap<BundleModuleName, AndroidManifest> androidManifestsToMergeByModule = HashMultimap.create();
Map<ZipPath, ModuleEntry> mergedEntriesByPath = new HashMap<>();
Optional<ResourceTable> mergedResourceTable = Optional.empty();
Map<String, TargetedAssetsDirectory> mergedAssetsConfig = new HashMap<>();
ApkTargeting mergedSplitTargeting = ApkTargeting.getDefaultInstance();
for (ModuleSplit split : splitsOfShard) {
// Resource tables and Split targetings can be merged for each split individually as we go.
mergedResourceTable = mergeResourceTables(mergedResourceTable, split);
mergedSplitTargeting = mergeSplitTargetings(mergedSplitTargeting, split);
// Android manifests need to be merged later, globally for all splits.
androidManifestsToMergeByModule.put(split.getModuleName(), split.getAndroidManifest());
for (ModuleEntry entry : split.getEntries()) {
if (entry.getPath().startsWith(DEX_DIRECTORY)) {
// Dex files need to be merged later, globally for all splits.
dexFilesToMergeByModule.put(split.getModuleName(), entry);
} else {
mergeEntries(mergedEntriesByPath, split, entry);
}
}
split.getAssetsConfig().ifPresent(assetsConfig -> {
mergeTargetedAssetsDirectories(mergedAssetsConfig, assetsConfig.getDirectoryList());
});
}
AndroidManifest mergedAndroidManifest = manifestMerger.merge(androidManifestsToMergeByModule);
Collection<ModuleEntry> mergedDexFiles = mergeDexFilesAndCache(dexFilesToMergeByModule, mergedAndroidManifest, mergedDexCache);
// Record names of the modules this shard was fused from.
ImmutableList<String> fusedModuleNames = getUniqueModuleNames(splitsOfShard);
if (mergedSplitType.equals(SplitType.STANDALONE)) {
mergedAndroidManifest = mergedAndroidManifest.toEditor().setFusedModuleNames(fusedModuleNames).save();
}
// Construct the final shard.
return buildShard(mergedEntriesByPath.values(), mergedDexFiles, mergedSplitTargeting, mergedAndroidManifest, mergedResourceTable, mergedAssetsConfig, mergedSplitType);
}
use of com.android.bundle.Files.TargetedAssetsDirectory in project bundletool by google.
the class SameTargetingMerger method mergeSplits.
private ModuleSplit mergeSplits(ImmutableCollection<ModuleSplit> splits) {
ModuleSplit.Builder builder = ModuleSplit.builder();
ImmutableList.Builder<ModuleEntry> entries = ImmutableList.builder();
AndroidManifest mergedManifest = null;
ResourceTable mergedResourceTable = null;
NativeLibraries mergedNativeConfig = null;
Map<String, TargetedAssetsDirectory> mergedAssetsConfig = new HashMap<>();
ApexImages mergedApexConfig = null;
ImmutableList<ApexEmbeddedApkConfig> mergedApexEmbeddedApkConfigs = null;
BundleModuleName mergedModuleName = null;
Boolean mergedIsMasterSplit = null;
VariantTargeting mergedVariantTargeting = null;
for (ModuleSplit split : splits) {
mergedManifest = getSameValueOrNonNull(mergedManifest, split.getAndroidManifest()).orElseThrow(() -> new IllegalStateException("Encountered two distinct manifests while merging."));
if (split.getResourceTable().isPresent()) {
mergedResourceTable = getSameValueOrNonNull(mergedResourceTable, split.getResourceTable().get()).orElseThrow(() -> new IllegalStateException("Unsupported case: encountered two distinct resource tables while " + "merging."));
}
if (split.getNativeConfig().isPresent()) {
mergedNativeConfig = getSameValueOrNonNull(mergedNativeConfig, split.getNativeConfig().get()).orElseThrow(() -> new IllegalStateException("Encountered two distinct native configs while merging."));
}
if (split.getApexConfig().isPresent()) {
mergedApexConfig = getSameValueOrNonNull(mergedApexConfig, split.getApexConfig().get()).orElseThrow(() -> new IllegalStateException("Encountered two distinct apex configs while merging."));
}
mergedApexEmbeddedApkConfigs = getSameValueOrNonNull(mergedApexEmbeddedApkConfigs, split.getApexEmbeddedApkConfigs()).orElseThrow(() -> new IllegalStateException("Encountered two distinct apex embedded apk configs while merging."));
mergedModuleName = getSameValueOrNonNull(mergedModuleName, split.getModuleName()).orElseThrow(() -> new IllegalStateException("Encountered two distinct module names while merging."));
mergedIsMasterSplit = getSameValueOrNonNull(mergedIsMasterSplit, Boolean.valueOf(split.isMasterSplit())).orElseThrow(() -> new IllegalStateException("Encountered conflicting isMasterSplit flag values while merging."));
mergedVariantTargeting = getSameValueOrNonNull(mergedVariantTargeting, split.getVariantTargeting()).orElseThrow(() -> new IllegalStateException("Encountered conflicting variant targeting values while merging."));
entries.addAll(split.getEntries());
builder.setApkTargeting(split.getApkTargeting());
split.getAssetsConfig().ifPresent(assetsConfig -> {
mergeTargetedAssetsDirectories(mergedAssetsConfig, assetsConfig.getDirectoryList());
});
}
if (mergedManifest != null) {
builder.setAndroidManifest(mergedManifest);
}
if (mergedResourceTable != null) {
builder.setResourceTable(mergedResourceTable);
}
if (mergedNativeConfig != null) {
builder.setNativeConfig(mergedNativeConfig);
}
if (!mergedAssetsConfig.isEmpty()) {
builder.setAssetsConfig(Assets.newBuilder().addAllDirectory(mergedAssetsConfig.values()).build());
}
if (mergedApexConfig != null) {
builder.setApexConfig(mergedApexConfig);
}
if (mergedApexEmbeddedApkConfigs != null) {
builder.setApexEmbeddedApkConfigs(mergedApexEmbeddedApkConfigs);
}
if (mergedModuleName != null) {
builder.setModuleName(mergedModuleName);
}
if (mergedIsMasterSplit != null) {
builder.setMasterSplit(mergedIsMasterSplit);
}
builder.setVariantTargeting(mergedVariantTargeting);
builder.setEntries(entries.build());
return builder.build();
}
Aggregations