use of com.android.tools.build.bundletool.model.ModuleEntry in project bundletool by google.
the class ZipFlingerApkSerializer method writeToZipFile.
private void writeToZipFile(ModuleSplit split, Path outputPath, TempDirectory tempDir) throws IOException {
checkFileDoesNotExist(outputPath);
createParentDirectories(outputPath);
split = apkSigner.signEmbeddedApks(split);
// Write a Proto-APK with only files that aapt2 requires as part of the convert command.
Path partialProtoApk = tempDir.getPath().resolve("proto.apk");
writeProtoApk(split, partialProtoApk, tempDir);
// Invoke aapt2 to convert files from proto to binary format.
Path binaryApkPath = tempDir.getPath().resolve("binary.apk");
if (enableSparseEncoding && split.getResourceTable().isPresent()) {
Path interimApk = tempDir.getPath().resolve("interim.apk");
aapt2.convertApkProtoToBinary(partialProtoApk, interimApk);
aapt2.optimizeToSparseResourceTables(interimApk, binaryApkPath);
} else {
aapt2.convertApkProtoToBinary(partialProtoApk, binaryApkPath);
}
checkState(Files.exists(binaryApkPath), "No APK created by aapt2 convert command.");
try (ZipArchive apkWriter = new ZipArchive(outputPath);
ZipReader aapt2ApkReader = ZipReader.createFromFile(binaryApkPath)) {
ImmutableMap<ZipPath, ModuleEntry> moduleEntriesByName = split.getEntries().stream().collect(toImmutableMap(entry -> ApkSerializerHelper.toApkEntryPath(entry.getPath()), entry -> entry, // e.g. base/assets/foo and base/root/assets/foo.
(a, b) -> b));
// Sorting entries by name for determinism.
ImmutableSortedSet<ZipPath> sortedEntryNames = Stream.concat(aapt2ApkReader.getEntries().keySet().stream().map(ZipPath::create), moduleEntriesByName.keySet().stream()).collect(toImmutableSortedSet(naturalOrder()));
ApkEntrySerializer apkEntrySerializer = new ApkEntrySerializer(apkWriter, aapt2ApkReader, split, tempDir);
for (ZipPath pathInApk : sortedEntryNames) {
Optional<Entry> aapt2Entry = aapt2ApkReader.getEntry(pathInApk.toString());
if (aapt2Entry.isPresent()) {
apkEntrySerializer.addAapt2Entry(pathInApk, aapt2Entry.get());
} else {
ModuleEntry moduleEntry = checkNotNull(moduleEntriesByName.get(pathInApk));
apkEntrySerializer.addRegularEntry(pathInApk, moduleEntry);
}
}
}
apkSigner.signApk(outputPath, split);
}
use of com.android.tools.build.bundletool.model.ModuleEntry in project bundletool by google.
the class ZipFlingerAppBundleSerializer method addEntriesFromSourceBundles.
/**
* Adds umodified entries to an archive, sourcing them from their original on-disk location.
*/
private static void addEntriesFromSourceBundles(ZipArchive archive, ImmutableListMultimap<BundleModule, ModuleEntry> entries) throws IOException {
Map<Path, ZipSource> bundleSources = new HashMap<>();
for (Map.Entry<BundleModule, ModuleEntry> moduleAndEntry : entries.entries()) {
BundleModule module = moduleAndEntry.getKey();
ModuleEntry moduleEntry = moduleAndEntry.getValue();
ModuleEntryBundleLocation location = moduleEntry.getBundleLocation().orElseThrow(IllegalStateException::new);
ZipPath entryFullPathInSourceBundle = location.entryPathInBundle();
ZipPath moduleDir = ZipPath.create(module.getName().toString());
ZipPath entryFullPathInDestBundle = moduleDir.resolve(moduleEntry.getPath());
Path pathToBundle = location.pathToBundle();
// We cannot use computeIfAbstent because new ZipSource may throw.
ZipSource entrySource = bundleSources.containsKey(pathToBundle) ? bundleSources.get(pathToBundle) : new ZipSource(pathToBundle);
bundleSources.putIfAbsent(pathToBundle, entrySource);
entrySource.select(entryFullPathInSourceBundle.toString(), /* newName= */
entryFullPathInDestBundle.toString());
}
for (ZipSource source : bundleSources.values()) {
archive.add(source);
}
}
use of com.android.tools.build.bundletool.model.ModuleEntry 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.tools.build.bundletool.model.ModuleEntry in project bundletool by google.
the class ModuleSplitsToShardMerger method mergeSingleApexShard.
@VisibleForTesting
ModuleSplit mergeSingleApexShard(ImmutableList<ModuleSplit> splitsOfShard) {
checkState(!splitsOfShard.isEmpty(), "A shard is made of at least one split.");
Map<ZipPath, ModuleEntry> mergedEntriesByPath = new HashMap<>();
ApkTargeting splitTargeting = ApkTargeting.getDefaultInstance();
for (ModuleSplit split : splitsOfShard) {
// An APEX shard is made of one master split and one multi-Abi split, so we use the latter.
splitTargeting = splitTargeting.hasMultiAbiTargeting() ? splitTargeting : split.getApkTargeting();
for (ModuleEntry entry : split.getEntries()) {
mergeEntries(mergedEntriesByPath, split, entry);
}
}
ModuleSplit shard = buildShard(mergedEntriesByPath.values(), ImmutableList.of(), splitTargeting, // An APEX module is made of one module, so any manifest works.
splitsOfShard.get(0).getAndroidManifest(), /* mergedResourceTable= */
Optional.empty(), /* mergedAssetsConfig= */
new HashMap<>(), /* mergedSplitType= */
SplitType.STANDALONE);
// Add the APEX config as it's used to identify APEX APKs.
return shard.toBuilder().setApexConfig(splitsOfShard.get(0).getApexConfig().get()).setApexEmbeddedApkConfigs(splitsOfShard.get(0).getApexEmbeddedApkConfigs()).build();
}
use of com.android.tools.build.bundletool.model.ModuleEntry in project bundletool by google.
the class ModuleSplitsToShardMerger method mergeEntries.
private static void mergeEntries(Map<ZipPath, ModuleEntry> mergedEntriesByPath, ModuleSplit split, ModuleEntry entry) {
ModuleEntry existingEntry = mergedEntriesByPath.putIfAbsent(entry.getPath(), entry);
// Any conflicts of plain entries should be caught by bundle validations.
checkState(existingEntry == null || existingEntry.equals(entry), "Module '%s' and some other module(s) contain entry '%s' with different contents.", split.getModuleName(), entry.getPath());
}
Aggregations