blob: 367a1ed2c82d346d8fae92b85ab1959b729926ec [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.base.sync.aspects;
import com.google.common.base.Objects;
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.async.process.ExternalTask;
import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
import com.google.idea.blaze.base.command.BlazeCommand;
import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.command.ExperimentalShowArtifactsLineProcessor;
import com.google.idea.blaze.base.experiments.BoolExperiment;
import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
import com.google.idea.blaze.base.metrics.Action;
import com.google.idea.blaze.base.model.SyncState;
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.prefetch.PrefetchService;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.Result;
import com.google.idea.blaze.base.scope.Scope;
import com.google.idea.blaze.base.scope.ScopedFunction;
import com.google.idea.blaze.base.scope.output.PerformanceWarning;
import com.google.idea.blaze.base.scope.output.PrintOutput;
import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
import com.google.idea.blaze.base.scope.scopes.TimingScope;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
import com.google.idea.blaze.base.sync.filediff.FileDiffService;
import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import javax.annotation.Nullable;
import java.io.File;
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;
/**
* Implementation of BlazeIdeInterface based on aspects.
*/
public class BlazeIdeInterfaceAspectsImpl extends BlazeIdeInterface {
private static final Logger LOG = Logger.getInstance(BlazeIdeInterfaceAspectsImpl.class);
private static final Label ANDROID_SDK_TARGET = new Label("//third_party/java/android/android_sdk_linux:android");
private static final FileDiffService fileDiffService = new FileDiffService();
private static final BoolExperiment USE_SKYLARK_ASPECT = new BoolExperiment("use.skylark.aspect", false);
static class State implements Serializable {
private static final long serialVersionUID = 10L;
ImmutableMap<Label, RuleIdeInfo> ruleMap;
File androidPlatformDirectory;
FileDiffService.State fileState = null;
Map<File, Label> fileToLabel = Maps.newHashMap();
WorkspaceLanguageSettings workspaceLanguageSettings;
String aspectStrategyName;
}
@Nullable
@Override
public IdeResult updateBlazeIdeState(Project project,
BlazeContext context,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
List<TargetExpression> targets,
WorkspaceLanguageSettings workspaceLanguageSettings,
ArtifactLocationDecoder artifactLocationDecoder,
SyncState.Builder syncStateBuilder,
@Nullable SyncState previousSyncState,
boolean requiresAndroidSdk) {
State prevState = previousSyncState != null ? previousSyncState.get(State.class) : null;
// If the language filter has changed, redo everything from scratch
if (prevState != null && !prevState.workspaceLanguageSettings.equals(workspaceLanguageSettings)) {
prevState = null;
}
// If the aspect strategy has changed, redo everything from scratch
final AspectStrategy aspectStrategy = getAspectStrategy();
if (prevState != null && !Objects.equal(prevState.aspectStrategyName, aspectStrategy.getName())) {
prevState = null;
}
List<File> fileList = getIdeInfo(project, context, workspaceRoot, projectViewSet, targets, aspectStrategy, requiresAndroidSdk);
if (!context.shouldContinue()) {
return null;
}
List<File> updatedFiles = Lists.newArrayList();
List<File> removedFiles = Lists.newArrayList();
FileDiffService.State fileState = fileDiffService.updateFiles(
prevState != null ? prevState.fileState : null,
fileList,
updatedFiles,
removedFiles
);
if (fileState == null) {
return null;
}
context.output(new PrintOutput(String.format(
"Total rules: %d, new/changed: %d, removed: %d",
fileList.size(),
updatedFiles.size(),
removedFiles.size()
)));
ListenableFuture<?> prefetchFuture = PrefetchService.getInstance().prefetchFiles(updatedFiles, true);
if (!FutureUtil.waitForFuture(context, prefetchFuture)
.timed("FetchAspectOutput")
.run()
.success()) {
return null;
}
State state = updateState(
context,
prevState,
fileState,
workspaceLanguageSettings,
artifactLocationDecoder,
aspectStrategy,
updatedFiles,
removedFiles
);
if (state == null) {
return null;
}
if (state.androidPlatformDirectory == null && requiresAndroidSdk) {
LOG.error("Android platform directory not found.");
return null;
}
syncStateBuilder.put(State.class, state);
return new IdeResult(state.ruleMap, state.androidPlatformDirectory);
}
private static List<File> getIdeInfo(Project project,
BlazeContext parentContext,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
List<TargetExpression> targets,
AspectStrategy aspectStrategy,
boolean addAndroidSdkTarget) {
return Scope.push(parentContext, context -> {
context.push(new TimingScope("ExecuteBlazeCommand"));
List<File> result = Lists.newArrayList();
BuildSystem buildSystem = Blaze.getBuildSystem(project);
BlazeCommand.Builder blazeCommandBuilder = BlazeCommand.builder(buildSystem, BlazeCommandName.BUILD);
if (addAndroidSdkTarget) {
blazeCommandBuilder.addTargets(ANDROID_SDK_TARGET);
}
blazeCommandBuilder
.addTargets(targets)
.addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS)
.addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
aspectStrategy.modifyIdeInfoCommand(blazeCommandBuilder);
int retVal = ExternalTask.builder(workspaceRoot, blazeCommandBuilder.build())
.context(context)
.stderr(LineProcessingOutputStream.of(
new ExperimentalShowArtifactsLineProcessor(result, aspectStrategy.getAspectOutputFileExtension()),
new IssueOutputLineProcessor(project, context, workspaceRoot)
))
.build()
.run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
if (retVal != 0) {
context.setHasError();
}
return result;
});
}
private static class RuleIdeInfoOrSdkInfo {
public File file;
public RuleIdeInfo ruleIdeInfo;
public File androidPlatformDirectory;
}
@Nullable
static State updateState(BlazeContext parentContext,
@Nullable State prevState,
FileDiffService.State fileState,
WorkspaceLanguageSettings workspaceLanguageSettings,
ArtifactLocationDecoder artifactLocationDecoder,
AspectStrategy aspectStrategy,
List<File> newFiles,
List<File> removedFiles) {
Result<State> result = Scope.push(parentContext, (ScopedFunction<Result<State>>)context -> {
context.push(new TimingScope("UpdateRuleMap"));
State state = new State();
state.fileState = fileState;
state.workspaceLanguageSettings = workspaceLanguageSettings;
state.aspectStrategyName = aspectStrategy.getName();
Map<Label, RuleIdeInfo> ruleMap = Maps.newHashMap();
Map<Label, RuleIdeInfo> updatedRules = Maps.newHashMap();
if (prevState != null) {
ruleMap.putAll(prevState.ruleMap);
state.androidPlatformDirectory = prevState.androidPlatformDirectory;
state.fileToLabel.putAll(prevState.fileToLabel);
}
// Update removed
for (File removedFile : removedFiles) {
Label label = state.fileToLabel.remove(removedFile);
if (label != null) {
ruleMap.remove(label);
}
}
AtomicLong totalSizeLoaded = new AtomicLong(0);
// Read protos from any new files
List<ListenableFuture<RuleIdeInfoOrSdkInfo>> futures = Lists.newArrayList();
for (File file : newFiles) {
futures.add(submit(() -> {
RuleIdeInfoOrSdkInfo ruleIdeInfoOrSdkInfo = new RuleIdeInfoOrSdkInfo();
ruleIdeInfoOrSdkInfo.file = file;
totalSizeLoaded.addAndGet(file.length());
AndroidStudioIdeInfo.RuleIdeInfo ruleProto = aspectStrategy.readAspectFile(file);
if (ruleProto.getLabel().equals(ANDROID_SDK_TARGET.toString())) {
ruleIdeInfoOrSdkInfo.androidPlatformDirectory = getAndroidPlatformDirectoryFromAndroidTarget(
ruleProto,
artifactLocationDecoder
);
}
else {
ruleIdeInfoOrSdkInfo.ruleIdeInfo = IdeInfoFromProtobuf.makeRuleIdeInfo(
workspaceLanguageSettings,
artifactLocationDecoder,
ruleProto
);
}
return ruleIdeInfoOrSdkInfo;
}
));
}
// Update state with result from proto files
int duplicateRuleLabels = 0;
try {
for (RuleIdeInfoOrSdkInfo ruleIdeInfoOrSdkInfo : Futures.allAsList(futures).get()) {
if (ruleIdeInfoOrSdkInfo.androidPlatformDirectory != null) {
state.androidPlatformDirectory = ruleIdeInfoOrSdkInfo.androidPlatformDirectory;
} else if (ruleIdeInfoOrSdkInfo.ruleIdeInfo != null) {
File file = ruleIdeInfoOrSdkInfo.file;
Label label = ruleIdeInfoOrSdkInfo.ruleIdeInfo.label;
RuleIdeInfo previousRule = updatedRules.putIfAbsent(label, ruleIdeInfoOrSdkInfo.ruleIdeInfo);
if (previousRule == null) {
state.fileToLabel.put(file, label);
} else {
duplicateRuleLabels++;
}
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return Result.error(null);
}
catch (ExecutionException e) {
return Result.error(e);
}
ruleMap.putAll(updatedRules);
context.output(new PrintOutput(String.format(
"Loaded %d aspect files, total size %dkB", newFiles.size(), totalSizeLoaded.get() / 1024
)));
if (duplicateRuleLabels > 0) {
context.output(new PerformanceWarning(String.format(
"There were %d duplicate rules. You may be including multiple configurations in your build. "
+ "Your IDE sync is slowed down by ~%d%%.",
duplicateRuleLabels,
(100 * duplicateRuleLabels / ruleMap.size())
)));
}
state.ruleMap = ImmutableMap.copyOf(ruleMap);
return Result.of(state);
});
if (result.error != null) {
LOG.error(result.error);
return null;
}
return result.result;
}
@Nullable
private static File getAndroidPlatformDirectoryFromAndroidTarget(AndroidStudioIdeInfo.RuleIdeInfo ruleProto,
ArtifactLocationDecoder artifactLocationDecoder) {
if (!ruleProto.hasJavaRuleIdeInfo()) {
return null;
}
AndroidStudioIdeInfo.JavaRuleIdeInfo javaRuleIdeInfo = ruleProto.getJavaRuleIdeInfo();
if (javaRuleIdeInfo.getJarsCount() == 0) {
return null;
}
AndroidStudioIdeInfo.LibraryArtifact libraryArtifact = javaRuleIdeInfo.getJars(0);
AndroidStudioIdeInfo.ArtifactLocation artifactLocation = libraryArtifact.getJar();
if (artifactLocation == null) {
return null;
}
File androidJar = artifactLocationDecoder.decode(artifactLocation).getFile();
return androidJar.getParentFile();
}
private static <T> ListenableFuture<T> submit(Callable<T> callable) {
return BlazeExecutor.getInstance().submit(callable);
}
@Override
public void resolveIdeArtifacts(Project project,
BlazeContext context,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
List<TargetExpression> targets) {
AspectStrategy aspectStrategy = getAspectStrategy();
BlazeCommand.Builder blazeCommandBuilder = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD)
.addTargets(targets)
.addBlazeFlags()
.addBlazeFlags(BlazeFlags.KEEP_GOING)
.addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder);
BlazeCommand blazeCommand = blazeCommandBuilder.build();
int retVal = ExternalTask.builder(workspaceRoot, blazeCommand)
.context(context)
.stderr(LineProcessingOutputStream.of(new IssueOutputLineProcessor(project, context, workspaceRoot)))
.build()
.run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
if (retVal != 0) {
context.setHasError();
}
}
private AspectStrategy getAspectStrategy() {
return USE_SKYLARK_ASPECT.getValue() ? AspectStrategy.SKYLARK_ASPECT : AspectStrategy.NATIVE_ASPECT;
}
}