Java tutorial
/* * 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.common.util.concurrent.ListeningExecutorService; 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.filecache.FileDiffer; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor; import com.google.idea.blaze.base.metrics.Action; import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; 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.aspects.strategy.AspectStrategy; import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProvider; import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; import javax.annotation.Nullable; /** Implementation of BlazeIdeInterface based on aspects. */ public class BlazeIdeInterfaceAspectsImpl implements BlazeIdeInterface { private static final Logger LOG = Logger.getInstance(BlazeIdeInterfaceAspectsImpl.class); static class State implements Serializable { private static final long serialVersionUID = 14L; TargetMap targetMap; ImmutableMap<File, Long> fileState = null; Map<File, TargetKey> fileToTargetMapKey = Maps.newHashMap(); WorkspaceLanguageSettings workspaceLanguageSettings; String aspectStrategyName; } @Override public IdeResult updateTargetMap(Project project, BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeVersionData blazeVersionData, List<TargetExpression> targets, WorkspaceLanguageSettings workspaceLanguageSettings, ArtifactLocationDecoder artifactLocationDecoder, SyncState.Builder syncStateBuilder, @Nullable SyncState previousSyncState, boolean mergeWithOldState) { 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(project, blazeVersionData); if (prevState != null && !Objects.equal(prevState.aspectStrategyName, aspectStrategy.getName())) { prevState = null; } IdeInfoResult ideInfoResult = getIdeInfo(project, context, workspaceRoot, projectViewSet, targets, aspectStrategy); if (ideInfoResult.buildResult == BuildResult.FATAL_ERROR) { return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR); } // If there was a partial error, make a best-effort attempt to sync. Retain // any old state that we have in an attempt not to lose too much code. if (ideInfoResult.buildResult == BuildResult.BUILD_ERROR) { mergeWithOldState = true; } List<File> fileList = ideInfoResult.files; List<File> updatedFiles = Lists.newArrayList(); List<File> removedFiles = Lists.newArrayList(); ImmutableMap<File, Long> fileState = FileDiffer.updateFiles(prevState != null ? prevState.fileState : null, fileList, updatedFiles, removedFiles); if (fileState == null) { return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR); } context.output(PrintOutput.log(String.format("Total rules: %d, new/changed: %d, removed: %d", fileList.size(), updatedFiles.size(), removedFiles.size()))); ListenableFuture<?> prefetchFuture = PrefetchService.getInstance().prefetchFiles(project, updatedFiles); if (!FutureUtil.waitForFuture(context, prefetchFuture).timed("FetchAspectOutput") .withProgressMessage("Reading IDE info result...").run().success()) { return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR); } State state = updateState(context, prevState, fileState, workspaceLanguageSettings, artifactLocationDecoder, aspectStrategy, updatedFiles, removedFiles, mergeWithOldState); if (state == null) { return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR); } syncStateBuilder.put(State.class, state); return new IdeResult(state.targetMap, ideInfoResult.buildResult); } private static class IdeInfoResult { private final List<File> files; private final BuildResult buildResult; IdeInfoResult(List<File> files, BuildResult buildResult) { this.files = files; this.buildResult = buildResult; } } private static IdeInfoResult getIdeInfo(Project project, BlazeContext parentContext, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, List<TargetExpression> targets, AspectStrategy aspectStrategy) { return Scope.push(parentContext, context -> { context.push(new TimingScope(String.format("Execute%sCommand", Blaze.buildSystemName(project)))); List<File> result = Lists.newArrayList(); BuildSystem buildSystem = Blaze.getBuildSystem(project); BlazeCommand.Builder blazeCommandBuilder = BlazeCommand.builder(buildSystem, BlazeCommandName.BUILD); blazeCommandBuilder.addTargets(targets); blazeCommandBuilder.addBlazeFlags(BlazeFlags.KEEP_GOING); blazeCommandBuilder.addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS) .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)); aspectStrategy.modifyIdeInfoCommand(blazeCommandBuilder); String fileExtension = aspectStrategy.getAspectOutputFileExtension(); String gzFileExtension = fileExtension + ".gz"; Predicate<String> fileFilter = fileName -> fileName.endsWith(fileExtension) || fileName.endsWith(gzFileExtension); int retVal = ExternalTask.builder(workspaceRoot).addBlazeCommand(blazeCommandBuilder.build()) .context(context) .stderr(LineProcessingOutputStream.of( new ExperimentalShowArtifactsLineProcessor(result, fileFilter), new IssueOutputLineProcessor(project, context, workspaceRoot))) .build().run(new LoggedTimingScope(project, Action.BLAZE_BUILD)); BuildResult buildResult = BuildResult.fromExitCode(retVal); return new IdeInfoResult(result, buildResult); }); } private static class TargetFilePair { private final File file; private final TargetIdeInfo target; TargetFilePair(File file, TargetIdeInfo target) { this.file = file; this.target = target; } } @Nullable static State updateState(BlazeContext parentContext, @Nullable State prevState, ImmutableMap<File, Long> fileState, WorkspaceLanguageSettings workspaceLanguageSettings, ArtifactLocationDecoder artifactLocationDecoder, AspectStrategy aspectStrategy, List<File> newFiles, List<File> removedFiles, boolean mergeWithOldState) { Result<State> result = Scope.push(parentContext, (ScopedFunction<Result<State>>) context -> { context.push(new TimingScope("UpdateTargetMap")); // If we're not removing we have to merge the old state // into the new one or we'll miss file removes next time ImmutableMap<File, Long> nextFileState = fileState; if (mergeWithOldState && prevState != null) { ImmutableMap.Builder<File, Long> fileStateBuilder = ImmutableMap.<File, Long>builder() .putAll(fileState); for (Map.Entry<File, Long> entry : prevState.fileState.entrySet()) { if (!fileState.containsKey(entry.getKey())) { fileStateBuilder.put(entry); } } nextFileState = fileStateBuilder.build(); } State state = new State(); state.fileState = nextFileState; state.workspaceLanguageSettings = workspaceLanguageSettings; state.aspectStrategyName = aspectStrategy.getName(); Map<TargetKey, TargetIdeInfo> targetMap = Maps.newHashMap(); Map<TargetKey, TargetIdeInfo> updatedTargets = Maps.newHashMap(); if (prevState != null) { targetMap.putAll(prevState.targetMap.map()); state.fileToTargetMapKey.putAll(prevState.fileToTargetMapKey); } // Update removed unless we're merging with the old state if (!mergeWithOldState) { for (File removedFile : removedFiles) { TargetKey key = state.fileToTargetMapKey.remove(removedFile); if (key != null) { targetMap.remove(key); } } } AtomicLong totalSizeLoaded = new AtomicLong(0); ListeningExecutorService executor = BlazeExecutor.getInstance().getExecutor(); // Read protos from any new files List<ListenableFuture<TargetFilePair>> futures = Lists.newArrayList(); for (File file : newFiles) { futures.add(executor.submit(() -> { totalSizeLoaded.addAndGet(file.length()); try (InputStream inputStream = getAspectInputStream(file)) { IntellijIdeInfo.TargetIdeInfo ruleProto = aspectStrategy.readAspectFile(inputStream); TargetIdeInfo target = IdeInfoFromProtobuf.makeTargetIdeInfo(workspaceLanguageSettings, ruleProto); return new TargetFilePair(file, target); } })); } // Update state with result from proto files int duplicateTargetLabels = 0; try { for (TargetFilePair targetFilePairs : Futures.allAsList(futures).get()) { if (targetFilePairs.target != null) { File file = targetFilePairs.file; TargetKey key = targetFilePairs.target.key; TargetIdeInfo previousTarget = updatedTargets.putIfAbsent(key, targetFilePairs.target); if (previousTarget == null) { state.fileToTargetMapKey.put(file, key); } else { duplicateTargetLabels++; } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return Result.error(null); } catch (ExecutionException e) { return Result.error(e); } targetMap.putAll(updatedTargets); context.output(PrintOutput.log(String.format("Loaded %d aspect files, total size %dkB", newFiles.size(), totalSizeLoaded.get() / 1024))); if (duplicateTargetLabels > 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%%.", duplicateTargetLabels, (100 * duplicateTargetLabels / targetMap.size())))); } state.targetMap = new TargetMap(ImmutableMap.copyOf(targetMap)); return Result.of(state); }); if (result.error != null) { LOG.error(result.error); return null; } return result.result; } private static InputStream getAspectInputStream(File file) throws IOException { InputStream inputStream = new FileInputStream(file); if (file.getName().endsWith(".gz")) { inputStream = new GZIPInputStream(inputStream); } return inputStream; } @Override public BuildResult resolveIdeArtifacts(Project project, BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeVersionData blazeVersionData, List<TargetExpression> targets) { return resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, blazeVersionData, targets, false); } @Override public BuildResult compileIdeArtifacts(Project project, BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeVersionData blazeVersionData, List<TargetExpression> targets) { boolean ideCompile = hasIdeCompileOutputGroup(blazeVersionData); return resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, blazeVersionData, targets, ideCompile); } private static boolean hasIdeCompileOutputGroup(BlazeVersionData blazeVersionData) { return blazeVersionData.bazelIsAtLeastVersion(0, 4, 3); } private static BuildResult resolveIdeArtifacts(Project project, BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeVersionData blazeVersionData, List<TargetExpression> targets, boolean useIdeCompileOutputGroup) { AspectStrategy aspectStrategy = getAspectStrategy(project, blazeVersionData); BlazeCommand.Builder blazeCommandBuilder = BlazeCommand .builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD).addTargets(targets).addBlazeFlags() .addBlazeFlags(BlazeFlags.KEEP_GOING).addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)); if (useIdeCompileOutputGroup) { aspectStrategy.modifyIdeCompileCommand(blazeCommandBuilder); } else { aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder); } BlazeCommand blazeCommand = blazeCommandBuilder.build(); int retVal = ExternalTask.builder(workspaceRoot).addBlazeCommand(blazeCommand).context(context) .stderr(LineProcessingOutputStream .of(new IssueOutputLineProcessor(project, context, workspaceRoot))) .build().run(new LoggedTimingScope(project, Action.BLAZE_BUILD)); return BuildResult.fromExitCode(retVal); } private static AspectStrategy getAspectStrategy(Project project, BlazeVersionData blazeVersionData) { for (AspectStrategyProvider provider : AspectStrategyProvider.EP_NAME.getExtensions()) { AspectStrategy aspectStrategy = provider.getAspectStrategy(project, blazeVersionData); if (aspectStrategy != null) { return aspectStrategy; } } // Should never get here throw new IllegalStateException("No aspect strategy found."); } }