in src/com/facebook/buck/android/NonPreDexedDexBuildable.java [630:903]
void addDexingSteps(
Set<Path> classpathEntriesToDex,
Supplier<ImmutableMap<String, HashCode>> classNamesToHashesSupplier,
Consumer<Path> secondaryDexDirectoriesConsumer,
ImmutableList.Builder<Step> steps,
Path primaryDexPath,
Optional<SourcePath> dexReorderToolFile,
Optional<SourcePath> dexReorderDataDumpFile,
ImmutableMultimap<APKModule, Path> additionalDexStoreToJarPathMap,
BuildContext buildContext) {
SourcePathResolverAdapter resolver = buildContext.getSourcePathResolver();
Supplier<Set<Path>> primaryInputsToDex;
Optional<Path> secondaryDexDir;
Optional<Supplier<Multimap<Path, Path>>> secondaryOutputToInputs;
Path secondaryDexParentDir = getSecondaryDexRoot().resolve("__secondary_dex__/");
Path additionalDexParentDir = getSecondaryDexRoot().resolve("__additional_dex__/");
Path additionalDexAssetsDir = additionalDexParentDir.resolve("assets");
Optional<ImmutableSet<Path>> additionalDexDirs;
if (shouldSplitDex) {
Optional<Path> proguardFullConfigFile = Optional.empty();
Optional<Path> proguardMappingFile = Optional.empty();
if (shouldProguard) {
proguardFullConfigFile = Optional.of(getProguardConfigDir().resolve("configuration.txt"));
proguardMappingFile = Optional.of(getProguardConfigDir().resolve("mapping.txt"));
}
// DexLibLoader expects that metadata.txt and secondary jar files are under this dir
// in assets.
// Intermediate directory holding the primary split-zip jar.
Path splitZipDir = getBinPath("__split_zip__");
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(), getProjectFilesystem(), splitZipDir)));
Path primaryJarPath = splitZipDir.resolve("primary.jar");
Path secondaryJarMetaDirParent = getSecondaryDexRoot().resolve("secondary_meta");
Path secondaryJarMetaDir =
secondaryJarMetaDirParent.resolve(AndroidBinary.SECONDARY_DEX_SUBDIR);
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
secondaryJarMetaDir)));
Path secondaryJarMeta = secondaryJarMetaDir.resolve("metadata.txt");
// Intermediate directory holding _ONLY_ the secondary split-zip jar files. This is
// important because SmartDexingCommand will try to dx every entry in this directory. It
// does this because it's impossible to know what outputs split-zip will generate until it
// runs.
Path secondaryZipDir = getBinPath("__secondary_zip__");
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(), getProjectFilesystem(), secondaryZipDir)));
// Intermediate directory holding the directories holding _ONLY_ the additional split-zip
// jar files that are intended for that dex store.
Path additionalDexStoresZipDir = getBinPath("__dex_stores_zip__");
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
additionalDexStoresZipDir)));
for (APKModule dexStore : additionalDexStoreToJarPathMap.keySet()) {
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
additionalDexStoresZipDir.resolve(dexStore.getName()))));
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
secondaryJarMetaDirParent.resolve("assets").resolve(dexStore.getName()))));
}
// Run the split-zip command which is responsible for dividing the large set of input
// classpaths into a more compact set of jar files such that no one jar file when dexed will
// yield a dex artifact too large for dexopt or the dx method limit to handle.
Path zipSplitReportDir = getBinPath("__split_zip_report__");
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(), getProjectFilesystem(), zipSplitReportDir)));
SplitZipStep splitZipCommand =
new SplitZipStep(
getProjectFilesystem(),
classpathEntriesToDex,
secondaryJarMeta,
primaryJarPath,
secondaryZipDir,
"secondary-%d.jar",
secondaryJarMetaDirParent,
additionalDexStoresZipDir,
proguardFullConfigFile,
proguardMappingFile,
skipProguard,
dexSplitMode,
dexSplitMode.getPrimaryDexScenarioFile().map(resolver::getAbsolutePath),
dexSplitMode.getPrimaryDexClassesFile().map(resolver::getAbsolutePath),
dexSplitMode.getSecondaryDexHeadClassesFile().map(resolver::getAbsolutePath),
dexSplitMode.getSecondaryDexTailClassesFile().map(resolver::getAbsolutePath),
additionalDexStoreToJarPathMap,
apkModuleMap,
rootAPKModule,
zipSplitReportDir);
steps.add(splitZipCommand);
// Add the secondary dex directory that has yet to be created, but will be by the
// smart dexing command. Smart dex will handle "cleaning" this directory properly.
if (reorderClassesIntraDex) {
secondaryDexDir =
Optional.of(secondaryDexParentDir.resolve(SMART_DEX_SECONDARY_DEX_SUBDIR));
Path intraDexReorderSecondaryDexDir =
secondaryDexParentDir.resolve(AndroidBinary.SECONDARY_DEX_SUBDIR);
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
secondaryDexDir.get())));
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
intraDexReorderSecondaryDexDir)));
} else {
secondaryDexDir =
Optional.of(secondaryDexParentDir.resolve(AndroidBinary.SECONDARY_DEX_SUBDIR));
steps.add(
MkdirStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
getProjectFilesystem(),
secondaryDexDir.get())));
}
if (additionalDexStoreToJarPathMap.isEmpty()) {
additionalDexDirs = Optional.empty();
} else {
ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
for (APKModule dexStore : additionalDexStoreToJarPathMap.keySet()) {
Path dexStorePath = additionalDexAssetsDir.resolve(dexStore.getName());
builder.add(dexStorePath);
steps.addAll(
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(), getProjectFilesystem(), dexStorePath)));
}
additionalDexDirs = Optional.of(builder.build());
}
if (dexSplitMode.getDexStore() == DexStore.RAW) {
secondaryDexDirectoriesConsumer.accept(secondaryDexDir.get());
} else {
secondaryDexDirectoriesConsumer.accept(secondaryJarMetaDirParent);
secondaryDexDirectoriesConsumer.accept(secondaryDexParentDir);
}
if (additionalDexDirs.isPresent()) {
secondaryDexDirectoriesConsumer.accept(additionalDexParentDir);
}
// Adjust smart-dex inputs for the split-zip case.
primaryInputsToDex = Suppliers.ofInstance(ImmutableSet.of(primaryJarPath));
Supplier<Multimap<Path, Path>> secondaryOutputToInputsMap =
splitZipCommand.getOutputToInputsMapSupplier(
secondaryDexDir.get(), additionalDexAssetsDir);
secondaryOutputToInputs = Optional.of(secondaryOutputToInputsMap);
} else {
// Simple case where our inputs are the natural classpath directories and we don't have
// to worry about secondary jar/dex files.
primaryInputsToDex = Suppliers.ofInstance(classpathEntriesToDex);
secondaryDexDir = Optional.empty();
secondaryOutputToInputs = Optional.empty();
}
HashInputJarsToDexStep hashInputJarsToDexStep =
new HashInputJarsToDexStep(
getProjectFilesystem(),
primaryInputsToDex,
secondaryOutputToInputs,
classNamesToHashesSupplier);
steps.add(hashInputJarsToDexStep);
// Stores checksum information from each invocation to intelligently decide when dx needs
// to be re-run.
Path successDir = getBinPath("__smart_dex__/.success");
steps.add(
MkdirStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(), getProjectFilesystem(), successDir)));
// Add the smart dexing tool that is capable of avoiding the external dx invocation(s) if
// it can be shown that the inputs have not changed. It also parallelizes dx invocations
// where applicable.
//
// Note that by not specifying the number of threads this command will use it will select an
// optimal default regardless of the value of --num-threads. This decision was made with the
// assumption that --num-threads specifies the threading of build rule execution and does not
// directly apply to the internal threading/parallelization details of various build commands
// being executed. For example, aapt is internally threaded by default when preprocessing
// images.
EnumSet<DxStep.Option> dxOptions =
shouldProguard
? EnumSet.of(DxStep.Option.NO_LOCALS)
: EnumSet.of(DxStep.Option.NO_OPTIMIZE);
Path selectedPrimaryDexPath = primaryDexPath;
if (reorderClassesIntraDex) {
String primaryDexFileName = primaryDexPath.getFileName().toString();
String smartDexPrimaryDexFileName = "smart-dex-" + primaryDexFileName;
selectedPrimaryDexPath =
Paths.get(
primaryDexPath.toString().replace(primaryDexFileName, smartDexPrimaryDexFileName));
}
SmartDexingStep smartDexingCommand =
new SmartDexingStep(
androidPlatformTarget,
buildContext,
getProjectFilesystem(),
Optional.of(selectedPrimaryDexPath),
Optional.of(primaryInputsToDex),
Optional.empty(),
secondaryDexDir,
secondaryOutputToInputs,
hashInputJarsToDexStep,
successDir,
dxOptions,
dxExecutorService,
xzCompressionLevel,
dxMaxHeapSize,
dexTool,
desugarInterfaceMethods,
true,
Optional.of(
additionalJarsForProguardAndDesugar.stream()
.map(input -> buildContext.getSourcePathResolver().getAbsolutePath(input))
.collect(ImmutableSet.toImmutableSet())),
getBuildTarget(),
minSdkVersion);
steps.add(smartDexingCommand);
if (reorderClassesIntraDex) {
IntraDexReorderStep intraDexReorderStep =
new IntraDexReorderStep(
buildContext,
getProjectFilesystem(),
resolver.getAbsolutePath(dexReorderToolFile.get()),
resolver.getAbsolutePath(dexReorderDataDumpFile.get()),
getBuildTarget(),
selectedPrimaryDexPath,
primaryDexPath,
secondaryOutputToInputs,
SMART_DEX_SECONDARY_DEX_SUBDIR,
AndroidBinary.SECONDARY_DEX_SUBDIR);
steps.add(intraDexReorderStep);
}
}