blob: e6f72e6c5b11258bf9357a6f68df5b8b25759b85 [file] [log] [blame]
/*
* Copyright (C) 2017 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.build.gradle.internal.transforms;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.build.api.transform.Context;
import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.Format;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.QualifiedContent.ContentType;
import com.android.build.api.transform.QualifiedContent.Scope;
import com.android.build.api.transform.Status;
import com.android.build.api.transform.Transform;
import com.android.build.api.transform.TransformException;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.android.build.api.transform.TransformOutputProvider;
import com.android.build.gradle.internal.LoggerWrapper;
import com.android.build.gradle.internal.pipeline.ExtendedContentType;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.builder.core.DefaultDexOptions;
import com.android.builder.core.DexOptions;
import com.android.builder.core.SerializableMessageReceiver;
import com.android.builder.dexing.ClassFileEntry;
import com.android.builder.dexing.ClassFileInput;
import com.android.builder.dexing.ClassFileInputs;
import com.android.builder.dexing.DexArchive;
import com.android.builder.dexing.DexArchiveBuilder;
import com.android.builder.dexing.DexArchiveBuilderConfig;
import com.android.builder.dexing.DexArchiveBuilderException;
import com.android.builder.dexing.DexArchives;
import com.android.builder.dexing.DexerTool;
import com.android.builder.dexing.r8.ClassFileProviderFactory;
import com.android.builder.utils.FileCache;
import com.android.dx.command.dexer.DxContext;
import com.android.ide.common.blame.Message;
import com.android.ide.common.blame.MessageReceiver;
import com.android.ide.common.blame.ParsingProcessOutputHandler;
import com.android.ide.common.blame.parser.DexParser;
import com.android.ide.common.blame.parser.ToolOutputParser;
import com.android.ide.common.internal.WaitableExecutor;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessOutput;
import com.android.ide.common.process.ProcessOutputHandler;
import com.android.utils.FileUtils;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import org.gradle.tooling.BuildException;
import org.gradle.workers.IsolationMode;
/**
* Transform that converts CLASS files to dex archives, {@link
* com.android.builder.dexing.DexArchive}. This will consume {@link TransformManager#CONTENT_CLASS},
* and for each of the inputs, corresponding dex archive will be produced.
*
* <p>This transform is incremental, only changed streams will be converted again. Additionally, if
* an input stream is able to provide a list of individual files that were changed, only those files
* will be processed. Their corresponding dex archives will be updated.
*/
public class DexArchiveBuilderTransform extends Transform {
private static final LoggerWrapper logger =
LoggerWrapper.getLogger(DexArchiveBuilderTransform.class);
public static final int DEFAULT_BUFFER_SIZE_IN_KB = 100;
// No more than 5 buckets should ever be necessary.
public static final int NUMBER_OF_BUCKETS =
Integer.min(4, Runtime.getRuntime().availableProcessors() / 2) + 1;
@NonNull private final Supplier<List<File>> androidJarClasspath;
@NonNull private final DexOptions dexOptions;
@NonNull private final MessageReceiver messageReceiver;
@VisibleForTesting @NonNull final WaitableExecutor executor;
private final int minSdkVersion;
@NonNull private final DexerTool dexer;
@NonNull private String projectVariant;
private boolean enableIncrementalDesugaring;
@NonNull private final DexArchiveBuilderCacheHandler cacheHandler;
private final boolean useGradleWorkers;
private final int inBufferSize;
private final int outBufferSize;
private final boolean isDebuggable;
@NonNull private final VariantScope.Java8LangSupport java8LangSupportType;
DexArchiveBuilderTransform(
@NonNull Supplier<List<File>> androidJarClasspath,
@NonNull DexOptions dexOptions,
@NonNull MessageReceiver messageReceiver,
@Nullable FileCache userLevelCache,
int minSdkVersion,
@NonNull DexerTool dexer,
boolean useGradleWorkers,
@Nullable Integer inBufferSize,
@Nullable Integer outBufferSize,
boolean isDebuggable,
@NonNull VariantScope.Java8LangSupport java8LangSupportType,
@NonNull String projectVariant,
boolean enableIncrementalDesugaring) {
this.androidJarClasspath = androidJarClasspath;
this.dexOptions = dexOptions;
this.messageReceiver = messageReceiver;
this.minSdkVersion = minSdkVersion;
this.dexer = dexer;
this.projectVariant = projectVariant;
this.enableIncrementalDesugaring = enableIncrementalDesugaring;
this.executor = WaitableExecutor.useGlobalSharedThreadPool();
this.cacheHandler =
new DexArchiveBuilderCacheHandler(
userLevelCache, dexOptions, minSdkVersion, isDebuggable, dexer);
this.useGradleWorkers = useGradleWorkers;
this.inBufferSize =
(inBufferSize == null ? DEFAULT_BUFFER_SIZE_IN_KB : inBufferSize) * 1024;
this.outBufferSize =
(outBufferSize == null ? DEFAULT_BUFFER_SIZE_IN_KB : outBufferSize) * 1024;
this.isDebuggable = isDebuggable;
this.java8LangSupportType = java8LangSupportType;
}
@NonNull
@Override
public String getName() {
return "dexBuilder";
}
@NonNull
@Override
public Set<ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@NonNull
@Override
public Set<ContentType> getOutputTypes() {
return ImmutableSet.of(ExtendedContentType.DEX_ARCHIVE);
}
@NonNull
@Override
public Set<? super Scope> getScopes() {
return TransformManager.SCOPE_FULL_WITH_IR_FOR_DEXING;
}
@NonNull
@Override
public Set<? super Scope> getReferencedScopes() {
return ImmutableSet.of(Scope.PROVIDED_ONLY, Scope.TESTED_CODE);
}
@NonNull
@Override
public Map<String, Object> getParameterInputs() {
try {
Map<String, Object> params = Maps.newHashMapWithExpectedSize(4);
params.put("optimize", !dexOptions.getAdditionalParameters().contains("--no-optimize"));
params.put("jumbo", dexOptions.getJumboMode());
params.put("min-sdk-version", minSdkVersion);
params.put("dex-builder-tool", dexer.name());
return params;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isIncremental() {
return true;
}
@Override
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, IOException, InterruptedException {
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
Preconditions.checkNotNull(outputProvider, "Missing output provider.");
if (dexOptions.getAdditionalParameters().contains("--no-optimize")) {
logger.warning(DefaultDexOptions.OPTIMIZE_WARNING);
}
logger.verbose("Task is incremental : %b ", transformInvocation.isIncremental());
if (!transformInvocation.isIncremental()) {
outputProvider.deleteAll();
}
Set<File> additionalPaths;
DesugarIncrementalTransformHelper desugarIncrementalTransformHelper;
if (!enableIncrementalDesugaring
|| java8LangSupportType != VariantScope.Java8LangSupport.D8) {
additionalPaths = ImmutableSet.of();
desugarIncrementalTransformHelper = null;
} else {
desugarIncrementalTransformHelper =
new DesugarIncrementalTransformHelper(
projectVariant, transformInvocation, executor);
additionalPaths =
desugarIncrementalTransformHelper
.getAdditionalPaths()
.stream()
.map(Path::toFile)
.collect(Collectors.toSet());
}
ClassFileProviderFactory classFileProviderFactory = new ClassFileProviderFactory();
List<DexArchiveBuilderCacheHandler.CacheableItem> cacheableItems = new ArrayList<>();
boolean isIncremental = transformInvocation.isIncremental();
try {
for (TransformInput input : transformInvocation.getInputs()) {
List<String> bootclasspath =
getBootClasspath(androidJarClasspath, java8LangSupportType);
List<String> classpath = getClasspath(transformInvocation, java8LangSupportType);
for (DirectoryInput dirInput : input.getDirectoryInputs()) {
logger.verbose("Dir input %s", dirInput.getFile().toString());
convertToDexArchive(
transformInvocation.getContext(),
dirInput,
outputProvider,
isIncremental,
classFileProviderFactory,
bootclasspath,
classpath,
additionalPaths);
}
for (JarInput jarInput : input.getJarInputs()) {
logger.verbose("Jar input %s", jarInput.getFile().toString());
D8DesugaringCacheInfo cacheInfo =
getD8DesugaringCacheInfo(
desugarIncrementalTransformHelper,
bootclasspath,
classpath,
jarInput);
List<File> dexArchives =
processJarInput(
transformInvocation.getContext(),
isIncremental,
jarInput,
outputProvider,
classFileProviderFactory,
bootclasspath,
classpath,
additionalPaths,
cacheInfo);
if (cacheInfo != D8DesugaringCacheInfo.DONT_CACHE && !dexArchives.isEmpty()) {
cacheableItems.add(
new DexArchiveBuilderCacheHandler.CacheableItem(
jarInput,
dexArchives,
cacheInfo.orderedD8DesugaringDependencies));
}
}
}
// all work items have been submitted, now wait for completion.
if (useGradleWorkers) {
transformInvocation.getContext().getWorkerExecutor().await();
} else {
executor.waitForTasksWithQuickFail(true);
}
// if we are in incremental mode, delete all removed files.
if (transformInvocation.isIncremental()) {
for (TransformInput transformInput : transformInvocation.getInputs()) {
removeDeletedEntries(outputProvider, transformInput);
}
}
// and finally populate the caches.
if (!cacheableItems.isEmpty()) {
cacheHandler.populateCache(cacheableItems);
}
logger.verbose("Done with all dex archive conversions");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new TransformException(e);
} catch (Exception e) {
logger.error(null, Throwables.getStackTraceAsString(e));
throw new TransformException(e);
}
}
@NonNull
private D8DesugaringCacheInfo getD8DesugaringCacheInfo(
@Nullable DesugarIncrementalTransformHelper desugarIncrementalTransformHelper,
@NonNull List<String> bootclasspath,
@NonNull List<String> classpath,
@NonNull JarInput jarInput) {
if (java8LangSupportType != VariantScope.Java8LangSupport.D8) {
return D8DesugaringCacheInfo.NO_INFO;
}
if (!enableIncrementalDesugaring) {
// Dependency graph is not available: lets state that we depend on all bootclasspath and
// classpath.
return new D8DesugaringCacheInfo(
Stream.concat(bootclasspath.stream(), classpath.stream())
.distinct()
.map(string -> Paths.get(string))
.collect(Collectors.toList()));
}
Preconditions.checkNotNull(desugarIncrementalTransformHelper);
Set<Path> unorderedD8DesugaringDependencies =
desugarIncrementalTransformHelper.getDependenciesPaths(jarInput.getFile().toPath());
// Don't cache libraries depending on class files in folders:
// Folders content is expected to change often so probably not worth paying the cache cost
// if we frequently need to rebuild anyway.
// Supporting dependency to class files would also require special care to respect order.
if (unorderedD8DesugaringDependencies
.stream()
.anyMatch(path -> !path.toString().endsWith(SdkConstants.DOT_JAR))) {
return D8DesugaringCacheInfo.DONT_CACHE;
}
// DesugaringGraph is not calculating the bootclasspath dependencies so just keep the full
// bootclasspath for now.
List<Path> bootclasspathPaths =
bootclasspath
.stream()
.distinct()
.map(string -> Paths.get(string))
.collect(Collectors.toList());
List<Path> classpathJars =
classpath
.stream()
.distinct()
.map((string) -> Paths.get(string))
.filter((path) -> unorderedD8DesugaringDependencies.contains(path))
.collect(Collectors.toList());
List<Path> allDependencies =
new ArrayList<>(bootclasspathPaths.size() + classpathJars.size());
allDependencies.addAll(bootclasspathPaths);
allDependencies.addAll(classpathJars);
return new D8DesugaringCacheInfo(allDependencies);
}
private static void removeDeletedEntries(
@NonNull TransformOutputProvider outputProvider, @NonNull TransformInput transformInput)
throws IOException {
for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
File outputFile = getPreDexFolder(outputProvider, directoryInput);
try (DexArchive output = DexArchives.fromInput(outputFile.toPath())) {
for (Map.Entry<File, Status> fileStatusEntry :
directoryInput.getChangedFiles().entrySet()) {
if (fileStatusEntry.getValue() == Status.REMOVED) {
Path relativePath =
directoryInput
.getFile()
.toPath()
.relativize(fileStatusEntry.getKey().toPath());
String fileToDelete;
if (fileStatusEntry.getKey().getName().endsWith(SdkConstants.DOT_CLASS)) {
fileToDelete = ClassFileEntry.withDexExtension(relativePath.toString());
} else {
fileToDelete = relativePath.toString();
}
output.removeFile(fileToDelete);
}
}
}
}
}
@NonNull
private List<File> processJarInput(
@NonNull Context context,
boolean isIncremental,
@NonNull JarInput jarInput,
@NonNull TransformOutputProvider transformOutputProvider,
@NonNull ClassFileProviderFactory classFileProviderFactory,
@NonNull List<String> bootclasspath,
@NonNull List<String> classpath,
@NonNull Set<File> additionalPaths,
@NonNull D8DesugaringCacheInfo cacheInfo)
throws Exception {
if (!isIncremental || additionalPaths.contains(jarInput.getFile())) {
Preconditions.checkState(
jarInput.getFile().exists(),
"File %s does not exist, yet it is reported as input. Try \n"
+ "cleaning the build directory.",
jarInput.getFile().toString());
return convertJarToDexArchive(
context,
jarInput,
transformOutputProvider,
classFileProviderFactory,
bootclasspath,
classpath,
cacheInfo);
} else if (jarInput.getStatus() != Status.NOTCHANGED) {
// delete all preDex jars if they exists.
for (int bucketId = 0; bucketId < NUMBER_OF_BUCKETS; bucketId++) {
File shardedOutput = getPreDexJar(transformOutputProvider, jarInput, bucketId);
FileUtils.deleteIfExists(shardedOutput);
if (jarInput.getStatus() != Status.REMOVED) {
FileUtils.mkdirs(shardedOutput.getParentFile());
}
}
File nonShardedOutput = getPreDexJar(transformOutputProvider, jarInput, null);
FileUtils.deleteIfExists(nonShardedOutput);
if (jarInput.getStatus() != Status.REMOVED) {
FileUtils.mkdirs(nonShardedOutput.getParentFile());
}
// and perform dexing if necessary.
if (jarInput.getStatus() == Status.ADDED || jarInput.getStatus() == Status.CHANGED) {
return convertJarToDexArchive(
context,
jarInput,
transformOutputProvider,
classFileProviderFactory,
bootclasspath,
classpath,
cacheInfo);
}
}
return ImmutableList.of();
}
private List<File> convertJarToDexArchive(
@NonNull Context context,
@NonNull JarInput toConvert,
@NonNull TransformOutputProvider transformOutputProvider,
@NonNull ClassFileProviderFactory classFileProviderFactory,
@NonNull List<String> bootclasspath,
@NonNull List<String> classpath,
@NonNull D8DesugaringCacheInfo cacheInfo)
throws Exception {
if (cacheInfo != D8DesugaringCacheInfo.DONT_CACHE) {
File cachedVersion =
cacheHandler.getCachedVersionIfPresent(
toConvert, cacheInfo.orderedD8DesugaringDependencies);
if (cachedVersion != null) {
File outputFile = getPreDexJar(transformOutputProvider, toConvert, null);
Files.copy(
cachedVersion.toPath(),
outputFile.toPath(),
StandardCopyOption.REPLACE_EXISTING);
// no need to try to cache an already cached version.
return ImmutableList.of();
}
}
return convertToDexArchive(
context,
toConvert,
transformOutputProvider,
false,
classFileProviderFactory,
bootclasspath,
classpath,
ImmutableSet.of());
}
public static class DexConversionParameters implements Serializable {
private final QualifiedContent input;
private final List<String> bootClasspath;
private final List<String> classpath;
private final String output;
private final int numberOfBuckets;
private final int buckedId;
private final int minSdkVersion;
private final List<String> dexAdditionalParameters;
private final int inBufferSize;
private final int outBufferSize;
private final DexerTool dexer;
private final boolean isDebuggable;
private final boolean isIncremental;
private final ClassFileProviderFactory classFileProviderFactory;
private final VariantScope.Java8LangSupport java8LangSupportType;
@NonNull private final Set<File> additionalPaths;
@Nonnull private final MessageReceiver messageReceiver;
public DexConversionParameters(
@NonNull QualifiedContent input,
@NonNull List<String> bootClasspath,
@NonNull List<String> classpath,
@NonNull File output,
int numberOfBuckets,
int buckedId,
int minSdkVersion,
@NonNull List<String> dexAdditionalParameters,
int inBufferSize,
int outBufferSize,
@NonNull DexerTool dexer,
boolean isDebuggable,
boolean isIncremental,
@NonNull ClassFileProviderFactory classFileProviderFactory,
@NonNull VariantScope.Java8LangSupport java8LangSupportType,
@NonNull Set<File> additionalPaths,
@Nonnull MessageReceiver messageReceiver) {
this.input = input;
this.bootClasspath = bootClasspath;
this.classpath = classpath;
this.numberOfBuckets = numberOfBuckets;
this.buckedId = buckedId;
this.output = output.toURI().toString();
this.minSdkVersion = minSdkVersion;
this.dexAdditionalParameters = dexAdditionalParameters;
this.inBufferSize = inBufferSize;
this.outBufferSize = outBufferSize;
this.dexer = dexer;
this.isDebuggable = isDebuggable;
this.isIncremental = isIncremental;
this.classFileProviderFactory = classFileProviderFactory;
this.java8LangSupportType = java8LangSupportType;
this.additionalPaths = additionalPaths;
this.messageReceiver = messageReceiver;
}
public boolean belongsToThisBucket(String path) {
return (Math.abs(path.hashCode()) % numberOfBuckets) == buckedId;
}
public boolean isDirectoryBased() {
return input instanceof DirectoryInput;
}
}
public static class DexConversionWorkAction implements Runnable {
private final DexConversionParameters dexConversionParameters;
@Inject
public DexConversionWorkAction(@NonNull DexConversionParameters dexConversionParameters) {
this.dexConversionParameters = dexConversionParameters;
}
@Override
public void run() {
try {
launchProcessing(
dexConversionParameters,
System.out,
System.err,
dexConversionParameters.messageReceiver);
} catch (Exception e) {
throw new BuildException(e.getMessage(), e);
}
}
}
private static class D8DesugaringCacheInfo {
@NonNull
private static final D8DesugaringCacheInfo NO_INFO =
new D8DesugaringCacheInfo(Collections.emptyList());
@NonNull
private static final D8DesugaringCacheInfo DONT_CACHE =
new D8DesugaringCacheInfo(Collections.emptyList());
@NonNull private final List<Path> orderedD8DesugaringDependencies;
private D8DesugaringCacheInfo(@NonNull List<Path> orderedD8DesugaringDependencies) {
this.orderedD8DesugaringDependencies = orderedD8DesugaringDependencies;
}
}
private static DexArchiveBuilder getDexArchiveBuilder(
int minSdkVersion,
@NonNull List<String> dexAdditionalParameters,
int inBufferSize,
int outBufferSize,
@NonNull List<String> bootClasspath,
@NonNull List<String> classpath,
@NonNull DexerTool dexer,
boolean isDebuggable,
@NonNull ClassFileProviderFactory classFileProviderFactory,
boolean d8DesugaringEnabled,
@NonNull OutputStream outStream,
@NonNull OutputStream errStream,
@NonNull MessageReceiver messageReceiver)
throws IOException {
DexArchiveBuilder dexArchiveBuilder;
switch (dexer) {
case DX:
boolean optimizedDex = !dexAdditionalParameters.contains("--no-optimize");
DxContext dxContext = new DxContext(outStream, errStream);
DexArchiveBuilderConfig config =
new DexArchiveBuilderConfig(
dxContext,
optimizedDex,
inBufferSize,
minSdkVersion,
DexerTool.DX,
outBufferSize,
DexArchiveBuilderCacheHandler.isJumboModeEnabledForDx());
dexArchiveBuilder = DexArchiveBuilder.createDxDexBuilder(config);
break;
case D8:
dexArchiveBuilder =
DexArchiveBuilder.createD8DexBuilder(
minSdkVersion,
isDebuggable,
bootClasspath
.stream()
.map(string -> Paths.get(string))
.collect(Collectors.toList()),
classpath
.stream()
.map(string -> Paths.get(string))
.collect(Collectors.toList()),
classFileProviderFactory,
d8DesugaringEnabled,
messageReceiver);
break;
default:
throw new AssertionError("Unknown dexer type: " + dexer.name());
}
return dexArchiveBuilder;
}
private List<File> convertToDexArchive(
@NonNull Context context,
@NonNull QualifiedContent input,
@NonNull TransformOutputProvider outputProvider,
boolean isIncremental,
@NonNull ClassFileProviderFactory classFileProviderFactory,
@NonNull List<String> bootClasspath,
@NonNull List<String> classpath,
@NonNull Set<File> additionalPaths)
throws Exception {
logger.verbose("Dexing %s", input.getFile().getAbsolutePath());
ImmutableList.Builder<File> dexArchives = ImmutableList.builder();
for (int bucketId = 0; bucketId < NUMBER_OF_BUCKETS; bucketId++) {
File preDexOutputFile = getPreDexFile(outputProvider, input, bucketId);
dexArchives.add(preDexOutputFile);
DexConversionParameters parameters =
new DexConversionParameters(
input,
bootClasspath,
classpath,
preDexOutputFile,
NUMBER_OF_BUCKETS,
bucketId,
minSdkVersion,
dexOptions.getAdditionalParameters(),
inBufferSize,
outBufferSize,
dexer,
isDebuggable,
isIncremental,
classFileProviderFactory,
java8LangSupportType,
additionalPaths,
new SerializableMessageReceiver(messageReceiver));
if (useGradleWorkers) {
context.getWorkerExecutor()
.submit(
DexConversionWorkAction.class,
configuration -> {
configuration.setIsolationMode(IsolationMode.NONE);
configuration.setParams(parameters);
});
} else {
executor.execute(
() -> {
ProcessOutputHandler outputHandler =
new ParsingProcessOutputHandler(
new ToolOutputParser(
new DexParser(), Message.Kind.ERROR, logger),
new ToolOutputParser(new DexParser(), logger),
messageReceiver);
ProcessOutput output = null;
try (Closeable ignored = output = outputHandler.createOutput()) {
launchProcessing(
parameters,
output.getStandardOutput(),
output.getErrorOutput(),
messageReceiver);
} finally {
if (output != null) {
try {
outputHandler.handleOutput(output);
} catch (ProcessException e) {
// ignore this one
}
}
}
return null;
});
}
}
return dexArchives.build();
}
private static void launchProcessing(
@NonNull DexConversionParameters dexConversionParameters,
@NonNull OutputStream outStream,
@NonNull OutputStream errStream,
@NonNull MessageReceiver receiver)
throws IOException, URISyntaxException {
DexArchiveBuilder dexArchiveBuilder =
getDexArchiveBuilder(
dexConversionParameters.minSdkVersion,
dexConversionParameters.dexAdditionalParameters,
dexConversionParameters.inBufferSize,
dexConversionParameters.outBufferSize,
dexConversionParameters.bootClasspath,
dexConversionParameters.classpath,
dexConversionParameters.dexer,
dexConversionParameters.isDebuggable,
dexConversionParameters.classFileProviderFactory,
VariantScope.Java8LangSupport.D8
== dexConversionParameters.java8LangSupportType,
outStream,
errStream,
receiver);
Path inputPath = dexConversionParameters.input.getFile().toPath();
Predicate<String> bucketFilter = dexConversionParameters::belongsToThisBucket;
boolean hasIncrementalInfo =
dexConversionParameters.isDirectoryBased() && dexConversionParameters.isIncremental;
Predicate<String> toProcess =
hasIncrementalInfo
? path -> {
File resolved = inputPath.resolve(path).toFile();
if (dexConversionParameters.additionalPaths.contains(resolved)) {
return true;
}
Map<File, Status> changedFiles =
((DirectoryInput) dexConversionParameters.input)
.getChangedFiles();
Status status = changedFiles.get(resolved);
return status == Status.ADDED || status == Status.CHANGED;
}
: path -> true;
bucketFilter = bucketFilter.and(toProcess);
logger.verbose("Dexing '" + inputPath + "' to '" + dexConversionParameters.output + "'");
try (ClassFileInput input = ClassFileInputs.fromPath(inputPath)) {
dexArchiveBuilder.convert(
input.entries(bucketFilter),
Paths.get(new URI(dexConversionParameters.output)),
dexConversionParameters.isDirectoryBased());
} catch (DexArchiveBuilderException ex) {
throw new DexArchiveBuilderException("Failed to process " + inputPath.toString(), ex);
}
}
@NonNull
private static List<String> getClasspath(
@NonNull TransformInvocation transformInvocation,
@NonNull VariantScope.Java8LangSupport java8LangSupportType)
throws IOException {
if (java8LangSupportType != VariantScope.Java8LangSupport.D8) {
return Collections.emptyList();
}
ImmutableList.Builder<String> classpathEntries = ImmutableList.builder();
Iterable<TransformInput> dependencies =
Iterables.concat(
transformInvocation.getInputs(), transformInvocation.getReferencedInputs());
classpathEntries.addAll(
TransformInputUtil.getDirectories(dependencies)
.stream()
.map(File::getPath)
.distinct()
.iterator());
classpathEntries.addAll(
Streams.stream(dependencies)
.flatMap(transformInput -> transformInput.getJarInputs().stream())
.filter(jarInput -> jarInput.getStatus() != Status.REMOVED)
.map(jarInput -> jarInput.getFile().getPath())
.distinct()
.iterator());
return classpathEntries.build();
}
@NonNull
private static List<String> getBootClasspath(
@NonNull Supplier<List<File>> androidJarClasspath,
@NonNull VariantScope.Java8LangSupport java8LangSupportType)
throws IOException {
if (java8LangSupportType != VariantScope.Java8LangSupport.D8) {
return Collections.emptyList();
}
ImmutableList.Builder<String> classpathEntries = ImmutableList.builder();
classpathEntries.addAll(
androidJarClasspath.get().stream().map(file -> file.getPath()).iterator());
return classpathEntries.build();
}
@NonNull
private static File getPreDexFile(
@NonNull TransformOutputProvider output,
@NonNull QualifiedContent qualifiedContent,
int bucketId) {
return qualifiedContent.getFile().isDirectory()
? getPreDexFolder(output, (DirectoryInput) qualifiedContent)
: getPreDexJar(output, (JarInput) qualifiedContent, bucketId);
}
@NonNull
private static File getPreDexJar(
@NonNull TransformOutputProvider output,
@NonNull JarInput qualifiedContent,
@Nullable Integer bucketId) {
return output.getContentLocation(
qualifiedContent.getName() + (bucketId == null ? "" : ("-" + bucketId)),
ImmutableSet.of(ExtendedContentType.DEX_ARCHIVE),
qualifiedContent.getScopes(),
Format.JAR);
}
@NonNull
private static File getPreDexFolder(
@NonNull TransformOutputProvider output, @NonNull DirectoryInput directoryInput) {
return FileUtils.mkdirs(
output.getContentLocation(
directoryInput.getName(),
ImmutableSet.of(ExtendedContentType.DEX_ARCHIVE),
directoryInput.getScopes(),
Format.DIRECTORY));
}
}