blob: 37a15d2ab4f193f89eb983b422d4ea24171f5a22 [file] [log] [blame]
/*
* Copyright (C) 2019 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.tasks;
import static com.android.builder.desugaring.DesugaringClassAnalyzer.analyze;
import com.android.annotations.NonNull;
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.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* This helper analyzes the task inputs, updates the {@link DesugaringGraph} it owns, and its
* main goal is to provide paths that should also be considered out of date, in addition to the
* changed files. See {@link #getAdditionalPaths()} for details.
*/
public class DesugarIncrementalHelper {
@NonNull
private static final LoggerWrapper logger =
LoggerWrapper.getLogger(DesugarIncrementalHelper.class);
@NonNull private final String projectVariant;
@NonNull private final Iterable<Path> allInputs;
@NonNull private final WaitableExecutor executor;
@NonNull private final Supplier<Set<Path>> changedPaths;
@NonNull private final Supplier<DesugaringGraph> desugaringGraph;
private final boolean isIncremental;
public DesugarIncrementalHelper(
@NonNull String projectVariant,
boolean isIncremental,
@NonNull Iterable<File> allInputs,
@NonNull Supplier<Set<Path>> changedPaths,
@NonNull WaitableExecutor executor) {
this.projectVariant = projectVariant;
this.isIncremental = isIncremental;
this.allInputs = Iterables.transform(allInputs, File::toPath);
this.executor = executor;
this.changedPaths = Suppliers.memoize(changedPaths::get);
DesugaringGraph graph;
if (!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
public Set<Path> getAdditionalPaths() {
if (!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 (!isIncremental) {
// Rebuild totally the graph whatever the cache status
return DesugaringGraphs.forVariant(
projectVariant, getInitalGraphData(allInputs, executor));
}
return DesugaringGraphs.forVariant(
projectVariant,
() -> getInitalGraphData(allInputs, executor),
() -> getIncrementalData(changedPaths, executor));
}
@NonNull
private static Collection<DesugaringData> getInitalGraphData(
@NonNull Iterable<Path> allInputs, @NonNull WaitableExecutor executor) {
Set<DesugaringData> data = Sets.newConcurrentHashSet();
for (Path input : allInputs) {
executor.execute(
() -> {
try {
if (Files.exists(input)) {
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 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;
}
public Set<Path> getDependenciesPaths(Path path) {
return desugaringGraph.get().getDependenciesPaths(path);
}
}