blob: 1cd2cee9b1dac45076bd0ecfed208b43ba426abb [file] [log] [blame]
/*
* Copyright 2016 The Bazel Authors. All rights reserved.
*
* 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.google.idea.blaze.java.sync.jdeps;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.base.async.FutureUtil;
import com.google.idea.blaze.base.async.executor.BlazeExecutor;
import com.google.idea.blaze.base.filecache.FileDiffer;
import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.model.SyncState;
import com.google.idea.blaze.base.prefetch.PrefetchService;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.Scope;
import com.google.idea.blaze.base.scope.output.PrintOutput;
import com.google.idea.blaze.base.scope.scopes.TimingScope;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.repackaged.devtools.build.lib.view.proto.Deps;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
/** Reads jdeps from the ide info result. */
public class JdepsFileReader {
private static final Logger logger = Logger.getInstance(JdepsFileReader.class);
static class JdepsState implements Serializable {
private static final long serialVersionUID = 4L;
private ImmutableMap<File, Long> fileState = null;
private Map<File, TargetKey> fileToTargetMap = Maps.newHashMap();
private Map<TargetKey, List<String>> targetToJdeps = Maps.newHashMap();
}
private static class Result {
File file;
TargetKey targetKey;
List<String> dependencies;
public Result(File file, TargetKey targetKey, List<String> dependencies) {
this.file = file;
this.targetKey = targetKey;
this.dependencies = dependencies;
}
}
/** Loads any updated jdeps files since the last invocation of this method. */
@Nullable
public JdepsMap loadJdepsFiles(
Project project,
BlazeContext parentContext,
ArtifactLocationDecoder artifactLocationDecoder,
Iterable<TargetIdeInfo> targetsToLoad,
SyncState.Builder syncStateBuilder,
@Nullable SyncState previousSyncState) {
JdepsState oldState =
previousSyncState != null ? previousSyncState.get(JdepsState.class) : null;
JdepsState jdepsState =
Scope.push(
parentContext,
(context) -> {
context.push(new TimingScope("LoadJdepsFiles"));
return doLoadJdepsFiles(
project, context, artifactLocationDecoder, oldState, targetsToLoad);
});
if (jdepsState == null) {
return null;
}
syncStateBuilder.put(JdepsState.class, jdepsState);
return targetKey -> jdepsState.targetToJdeps.get(targetKey);
}
private JdepsState doLoadJdepsFiles(
Project project,
BlazeContext context,
ArtifactLocationDecoder artifactLocationDecoder,
@Nullable JdepsState oldState,
Iterable<TargetIdeInfo> targetsToLoad) {
JdepsState state = new JdepsState();
if (oldState != null) {
state.targetToJdeps = Maps.newHashMap(oldState.targetToJdeps);
state.fileToTargetMap = Maps.newHashMap(oldState.fileToTargetMap);
}
Map<File, TargetKey> fileToTargetMap = Maps.newHashMap();
for (TargetIdeInfo target : targetsToLoad) {
assert target != null;
JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
if (javaIdeInfo != null) {
ArtifactLocation jdepsFile = javaIdeInfo.jdepsFile;
if (jdepsFile != null) {
fileToTargetMap.put(artifactLocationDecoder.decode(jdepsFile), target.key);
}
}
}
List<File> updatedFiles = Lists.newArrayList();
List<File> removedFiles = Lists.newArrayList();
state.fileState =
FileDiffer.updateFiles(
oldState != null ? oldState.fileState : null,
fileToTargetMap.keySet(),
updatedFiles,
removedFiles);
ListenableFuture<?> fetchFuture =
PrefetchService.getInstance().prefetchFiles(project, updatedFiles, true);
if (!FutureUtil.waitForFuture(context, fetchFuture)
.timed("FetchJdeps")
.withProgressMessage("Reading jdeps files...")
.run()
.success()) {
return null;
}
for (File removedFile : removedFiles) {
TargetKey targetKey = state.fileToTargetMap.remove(removedFile);
if (targetKey != null) {
state.targetToJdeps.remove(targetKey);
}
}
AtomicLong totalSizeLoaded = new AtomicLong(0);
List<ListenableFuture<Result>> futures = Lists.newArrayList();
for (File updatedFile : updatedFiles) {
futures.add(
submit(
() -> {
totalSizeLoaded.addAndGet(updatedFile.length());
try (InputStream inputStream = new FileInputStream(updatedFile)) {
Deps.Dependencies dependencies = Deps.Dependencies.parseFrom(inputStream);
if (dependencies != null) {
List<String> dependencyStringList = Lists.newArrayList();
for (Deps.Dependency dependency : dependencies.getDependencyList()) {
// We only want explicit or implicit deps that were
// actually resolved by the compiler, not ones that are
// available for use in the same package
if (dependency.getKind() == Deps.Dependency.Kind.EXPLICIT
|| dependency.getKind() == Deps.Dependency.Kind.IMPLICIT) {
dependencyStringList.add(dependency.getPath());
}
}
TargetKey targetKey = fileToTargetMap.get(updatedFile);
return new Result(updatedFile, targetKey, dependencyStringList);
}
} catch (FileNotFoundException e) {
logger.info("Could not open jdeps file: " + updatedFile);
}
return null;
}));
}
try {
for (Result result : Futures.allAsList(futures).get()) {
if (result != null) {
state.fileToTargetMap.put(result.file, result.targetKey);
state.targetToJdeps.put(result.targetKey, result.dependencies);
}
}
context.output(
PrintOutput.log(
String.format(
"Loaded %d jdeps files, total size %dkB",
updatedFiles.size(), totalSizeLoaded.get() / 1024)));
} catch (InterruptedException | ExecutionException e) {
logger.error(e);
return null;
}
return state;
}
private static <T> ListenableFuture<T> submit(Callable<T> callable) {
return BlazeExecutor.getInstance().submit(callable);
}
}