diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml index 04da8ba721c..6763f1c69ff 100644 --- a/base/src/META-INF/blaze-base.xml +++ b/base/src/META-INF/blaze-base.xml @@ -525,6 +525,7 @@ + diff --git a/base/src/com/google/idea/blaze/base/bazel/AbstractBuildInvoker.java b/base/src/com/google/idea/blaze/base/bazel/AbstractBuildInvoker.java index 003993b1e32..5e76138a86c 100644 --- a/base/src/com/google/idea/blaze/base/bazel/AbstractBuildInvoker.java +++ b/base/src/com/google/idea/blaze/base/bazel/AbstractBuildInvoker.java @@ -28,6 +28,7 @@ import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelperBep; import com.google.idea.blaze.base.command.info.BlazeInfo; +import com.google.idea.blaze.base.command.info.BlazeInfoProvider; import com.google.idea.blaze.base.command.info.BlazeInfoRunner; import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; @@ -144,7 +145,15 @@ private ListenableFuture runBlazeInfo() { BlazeCommandName.INFO, blazeContext, BlazeInvocationContext.SYNC_CONTEXT); + if (BlazeInfoProvider.isEnabled()) { + return BlazeInfoProvider.getInstance(project) + .getBlazeInfo(blazeContext, syncFlags); + } return BlazeInfoRunner.getInstance() - .runBlazeInfo(project, this, blazeContext, buildSystem.getName(), syncFlags); + .runBlazeInfo(project, + this, + blazeContext, + buildSystem.getName(), + syncFlags); } } diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeInfoProvider.java b/base/src/com/google/idea/blaze/base/command/info/BlazeInfoProvider.java new file mode 100644 index 00000000000..8371292a7d6 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfoProvider.java @@ -0,0 +1,136 @@ +/* + * Copyright 2023 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.command.info; + +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.base.async.executor.BlazeExecutor; +import com.google.idea.blaze.base.bazel.BuildSystem; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.sync.SyncListener; +import com.google.idea.blaze.base.sync.SyncMode; +import com.google.idea.blaze.base.sync.SyncResult; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +/** + *

Providing a {@link com.google.idea.blaze.base.command.info.BlazeInfo}, the result will be cached and invalidated on any sync failure.

+ * + * @see BlazeInfoRunner + */ +public class BlazeInfoProvider { + private static final Logger logger = Logger.getInstance(BlazeInfoProvider.class); + private static final BoolExperiment enabled = + new BoolExperiment("blaze.info.provider.enabled", false); + + private final Project project; + private volatile BlazeInfo blazeInfo; + + public BlazeInfoProvider(Project project) { + this.project = project; + } + + public static BlazeInfoProvider getInstance(Project project) { + return project.getService(BlazeInfoProvider.class); + } + + public static Boolean isEnabled() { + return enabled.getValue(); + } + + public ListenableFuture getBlazeInfo( + BlazeContext context, + List blazeFlags, + String key) { + ListenableFuture future = getBlazeInfo(context, + blazeFlags); + return Futures.transform(future, + info -> info.get(key), + BlazeExecutor.getInstance().getExecutor() + ); + } + + public ListenableFuture getBlazeInfo( + BlazeContext context, + List blazeFlags) { + return BlazeExecutor.getInstance().submit(() -> { + BlazeInfo info = getCachedBlazeInfo(context, + blazeFlags); + if (info == null) { + throw new BlazeInfoException("Unable to get blaze info"); + } + return info; + }); + } + + private @Nullable BlazeInfo getCachedBlazeInfo( + BlazeContext context, + List blazeFlags) { + if (blazeInfo != null) { + logger.debug("Returning cached BlazeInfo"); + return blazeInfo; + } + try { + BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings(); + if (importSettings == null) { + return null; + } + BuildSystem.BuildInvoker buildInvoker = Blaze.getBuildSystemProvider(project) + .getBuildSystem() + .getDefaultInvoker(project, context); + logger.debug("Running bazel info"); + blazeInfo = BlazeInfoRunner.getInstance().runBlazeInfo( + project, + buildInvoker, + BlazeContext.create(), + importSettings.getBuildSystem(), + blazeFlags + ).get(); + return blazeInfo; + } catch (InterruptedException | ExecutionException e) { + logger.warn("Unable to run blaze info", e); + return null; + } + } + + public void invalidate() { + logger.debug("invalidating"); + blazeInfo = null; + } + + public static final class Invalidator implements SyncListener { + @Override + public void afterSync( + Project project, + BlazeContext context, + SyncMode syncMode, + SyncResult syncResult, + ImmutableSet buildIds) { + if(!syncResult.successful()) { + BlazeInfoProvider.getInstance(project).invalidate(); + } + } + } +} diff --git a/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java b/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java index 011e8cdebe6..14dbb8ddab6 100644 --- a/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java +++ b/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java @@ -26,6 +26,7 @@ import com.google.idea.blaze.base.command.BlazeFlags; import com.google.idea.blaze.base.command.BlazeInvocationContext; import com.google.idea.blaze.base.command.info.BlazeInfo; +import com.google.idea.blaze.base.command.info.BlazeInfoProvider; import com.google.idea.blaze.base.command.info.BlazeInfoRunner; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.model.BlazeVersionData; @@ -115,15 +116,7 @@ private SyncProjectState getProjectState(BlazeContext context, BlazeSyncParams p BlazeInvocationContext.SYNC_CONTEXT); ListenableFuture blazeInfoFuture = - BlazeInfoRunner.getInstance() - .runBlazeInfo( - project, - Blaze.getBuildSystemProvider(project) - .getBuildSystem() - .getDefaultInvoker(project, context), - context, - importSettings.getBuildSystem(), - syncFlags); + createBazelInfoFuture(context, syncFlags, params.syncMode()); ListenableFuture workingSetFuture; if(params.addWorkingSet() || params.syncMode() == SyncMode.FULL) { @@ -189,6 +182,29 @@ private SyncProjectState getProjectState(BlazeContext context, BlazeSyncParams p .build(); } + private ListenableFuture createBazelInfoFuture( + BlazeContext context, + List syncFlags, + SyncMode syncMode) { + boolean useBazelInfoRunner = !BlazeInfoProvider.isEnabled() || syncMode == SyncMode.FULL; + if (useBazelInfoRunner) { + return BlazeInfoRunner.getInstance() + .runBlazeInfo( + project, + Blaze.getBuildSystemProvider(project) + .getBuildSystem() + .getDefaultInvoker(project, + context), + context, + importSettings.getBuildSystem(), + syncFlags); + } + return BlazeInfoProvider.getInstance(project) + .getBlazeInfo( + context, + syncFlags); + } + private static class WorkspacePathResolverAndProjectView { final WorkspacePathResolver workspacePathResolver; final ProjectViewSet projectViewSet;