blob: 3eb3f629175dc3096624bce15c8b563972445b74 [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 static com.android.builder.desugaring.DesugaringClassAnalyzer.analyze;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.Status;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.android.build.gradle.internal.LoggerWrapper;
import com.android.builder.desugaring.DesugaringClassAnalyzer;
import com.android.builder.desugaring.DesugaringData;
import com.android.builder.desugaring.DesugaringGraph;
import com.android.builder.desugaring.DesugaringGraphs;
import com.android.ide.common.internal.WaitableExecutor;
import com.google.common.base.Stopwatch;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* This helper analyzes the transform inputs, updates the {@link DesugaringGraph} it owns, and its
* main goal is to provide paths that should also be also considered out of date, in addition to the
* changed files. See {@link #getAdditionalPaths()} for details.
*/
class DesugarIncrementalTransformHelper {
@NonNull
private static final LoggerWrapper logger =
LoggerWrapper.getLogger(DesugarIncrementalTransformHelper.class);
@NonNull private final String projectVariant;
@NonNull private final TransformInvocation invocation;
@NonNull private final WaitableExecutor executor;
@NonNull
private final Supplier<Set<Path>> changedPaths = Suppliers.memoize(this::findChangedPaths);
@NonNull private final Supplier<DesugaringGraph> desugaringGraph;
DesugarIncrementalTransformHelper(
@NonNull String projectVariant,
@NonNull TransformInvocation invocation,
@NonNull WaitableExecutor executor) {
this.projectVariant = projectVariant;
this.invocation = invocation;
this.executor = executor;
DesugaringGraph graph;
if (!invocation.isIncremental()) {
DesugaringGraphs.invalidate(projectVariant);
graph = null;
} else {
graph =
DesugaringGraphs.updateVariant(
projectVariant, () -> getIncrementalData(changedPaths, executor));
}
desugaringGraph =
graph != null ? () -> graph : Suppliers.memoize(this::makeDesugaringGraph);
}
/**
* Get the list of paths that should be re-desugared, and update the dependency graph.
*
* <p>For full builds, graph will be invalidated. No additional paths to process are returned,
* as all inputs are considered out-of-date, and will be re-processed.
*
* <p>In incremental builds, graph will be initialized (if not already), or updated
* incrementally. Once it has been populated, set of changed files is analyzed, and all
* impacted, non-changed, paths will be returned as a result.
*/
@NonNull
Set<Path> getAdditionalPaths() throws InterruptedException {
if (!invocation.isIncremental()) {
return ImmutableSet.of();
}
Stopwatch stopwatch = Stopwatch.createStarted();
logger.verbose("Desugaring dependencies incrementally.");
Set<Path> additionalPaths = new HashSet<>();
for (Path changed : changedPaths.get()) {
for (Path path : desugaringGraph.get().getDependentPaths(changed)) {
if (!changedPaths.get().contains(path)) {
additionalPaths.add(path);
}
}
}
logger.verbose(
"Time to calculate desugaring dependencies: %d",
stopwatch.elapsed(TimeUnit.MILLISECONDS));
logger.verbose("Additional paths to desugar: %s", additionalPaths.toString());
return additionalPaths;
}
@NonNull
private DesugaringGraph makeDesugaringGraph() {
if (!invocation.isIncremental()) {
// Rebuild totally the graph whatever the cache status
return DesugaringGraphs.forVariant(
projectVariant, getInitalGraphData(invocation, executor));
}
return DesugaringGraphs.forVariant(
projectVariant,
() -> getInitalGraphData(invocation, executor),
() -> getIncrementalData(changedPaths, executor));
}
@NonNull
private static Collection<DesugaringData> getInitalGraphData(
@NonNull TransformInvocation invocation, @NonNull WaitableExecutor executor) {
Set<DesugaringData> data = Sets.newConcurrentHashSet();
for (TransformInput input : getAllInputs(invocation)) {
for (QualifiedContent qualifiedContent :
Iterables.concat(input.getDirectoryInputs(), input.getJarInputs())) {
executor.execute(
() -> {
Path toProcess = qualifiedContent.getFile().toPath();
try {
if (Files.exists(toProcess)) {
data.addAll(analyze(toProcess));
}
return null;
} catch (Throwable t) {
logger.error(t, "error processing %s", toProcess);
throw t;
}
});
}
}
try {
executor.waitForTasksWithQuickFail(true);
} catch (InterruptedException e) {
throw new RuntimeException("Unable to get desugaring graph", e);
}
return data;
}
@NonNull
private static Set<DesugaringData> getIncrementalData(
@NonNull Supplier<Set<Path>> changedPaths, @NonNull WaitableExecutor executor) {
Set<DesugaringData> data = Sets.newConcurrentHashSet();
for (Path input : changedPaths.get()) {
if (Files.notExists(input)) {
data.add(DesugaringClassAnalyzer.forRemoved(input));
} else {
executor.execute(
() -> {
try {
data.addAll(analyze(input));
return null;
} catch (Throwable t) {
logger.error(t, "error processing %s", input);
throw t;
}
});
}
}
try {
executor.waitForTasksWithQuickFail(true);
} catch (InterruptedException e) {
throw new RuntimeException("Unable to get desugaring graph", e);
}
return data;
}
@NonNull
private Set<Path> findChangedPaths() {
Set<Path> changedPaths = Sets.newHashSet();
for (TransformInput input : getAllInputs(invocation)) {
for (DirectoryInput dirInput : input.getDirectoryInputs()) {
Map<Status, Set<File>> byStatus = TransformInputUtil.getByStatus(dirInput);
for (File modifiedFile :
Iterables.concat(
byStatus.get(Status.CHANGED),
byStatus.get(Status.REMOVED),
byStatus.get(Status.ADDED))) {
if (modifiedFile.toString().endsWith(SdkConstants.DOT_CLASS)) {
changedPaths.add(modifiedFile.toPath());
}
}
}
for (JarInput jarInput : input.getJarInputs()) {
if (jarInput.getStatus() != Status.NOTCHANGED) {
changedPaths.add(jarInput.getFile().toPath());
}
}
}
return changedPaths;
}
@NonNull
private static Iterable<TransformInput> getAllInputs(@NonNull TransformInvocation invocation) {
return Iterables.concat(invocation.getInputs(), invocation.getReferencedInputs());
}
public Set<Path> getDependenciesPaths(Path path) {
return desugaringGraph.get().getDependenciesPaths(path);
}
}