From 992d231bbe71e0e9cd048bd1e249da1258cddf15 Mon Sep 17 00:00:00 2001 From: Mihai Toader Date: Wed, 28 Aug 2024 12:24:55 -0700 Subject: [PATCH] [#6664] Add `blaze mod ...` runner implementation - 2/n --- base/src/META-INF/blaze-base.xml | 2 + .../blaze/base/command/BlazeCommandName.java | 1 + .../base/command/BlazeCommandRunner.java | 8 + .../CommandLineBlazeCommandRunner.java | 35 +++++ .../base/command/mod/BlazeModException.java | 16 ++ .../base/command/mod/BlazeModRunner.java | 60 ++++++++ .../base/command/mod/BlazeModRunnerImpl.java | 76 ++++++++++ .../base/bazel/FakeBlazeCommandRunner.java | 141 ++++++++++-------- 8 files changed, 275 insertions(+), 64 deletions(-) create mode 100644 base/src/com/google/idea/blaze/base/command/mod/BlazeModException.java create mode 100644 base/src/com/google/idea/blaze/base/command/mod/BlazeModRunner.java create mode 100644 base/src/com/google/idea/blaze/base/command/mod/BlazeModRunnerImpl.java diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml index ee02185227b..313f805dac1 100644 --- a/base/src/META-INF/blaze-base.xml +++ b/base/src/META-INF/blaze-base.xml @@ -281,6 +281,8 @@ serviceImplementation="com.google.idea.blaze.base.io.VirtualFileSystemProviderImpl"/> + diff --git a/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java b/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java index 4e340993c56..bdc18dce2ab 100644 --- a/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java +++ b/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java @@ -41,6 +41,7 @@ public final class BlazeCommandName { public static final BlazeCommandName INFO = fromString("info"); public static final BlazeCommandName MOBILE_INSTALL = fromString("mobile-install"); public static final BlazeCommandName COVERAGE = fromString("coverage"); + public static final BlazeCommandName MOD = fromString("mod"); public static BlazeCommandName fromString(String name) { knownCommands.putIfAbsent(name, new BlazeCommandName(name)); diff --git a/base/src/com/google/idea/blaze/base/command/BlazeCommandRunner.java b/base/src/com/google/idea/blaze/base/command/BlazeCommandRunner.java index 3122e52bc3c..ebe3ffb2c69 100644 --- a/base/src/com/google/idea/blaze/base/command/BlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/BlazeCommandRunner.java @@ -77,6 +77,14 @@ InputStream runBlazeInfo( BlazeContext context) throws BuildException; + @MustBeClosed + InputStream runBlazeMod( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context) + throws BuildException; + /** Allows enabling the use of command runner for restricted set of users. */ default boolean canUseCli() { return true; diff --git a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java index 84d54e599e0..11a4c7043ea 100644 --- a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java @@ -224,6 +224,41 @@ public InputStream runBlazeInfo( } } + @Override + @MustBeClosed + public InputStream runBlazeMod( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context) + throws BuildException { + performGuardCheckAsBuildException(project, context); + + try (Closer closer = Closer.create()) { + Path tmpFile = + Files.createTempFile( + String.format("intellij-bazel-%s-", blazeCommandBuilder.build().getName()), + ".stdout"); + OutputStream out = closer.register(Files.newOutputStream(tmpFile)); + OutputStream stderr = + closer.register(LineProcessingOutputStream.of(new PrintOutputLineProcessor(context))); + int exitCode = + ExternalTask.builder(WorkspaceRoot.fromProject(project)) + .addBlazeCommand(blazeCommandBuilder.build()) + .context(context) + .stdout(out) + .stderr(stderr) + .ignoreExitCode(true) + .build() + .run(); + BazelExitCodeException.throwIfFailed(blazeCommandBuilder, exitCode); + return new BufferedInputStream( + Files.newInputStream(tmpFile, StandardOpenOption.DELETE_ON_CLOSE)); + } catch (IOException e) { + throw new BuildException(e); + } + } + private BuildResult issueBuild( BlazeCommand.Builder blazeCommandBuilder, WorkspaceRoot workspaceRoot, Map envVars, BlazeContext context) { blazeCommandBuilder.addBlazeFlags(getExtraBuildFlags(blazeCommandBuilder)); diff --git a/base/src/com/google/idea/blaze/base/command/mod/BlazeModException.java b/base/src/com/google/idea/blaze/base/command/mod/BlazeModException.java new file mode 100644 index 00000000000..9d097381af5 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/mod/BlazeModException.java @@ -0,0 +1,16 @@ +package com.google.idea.blaze.base.command.mod; + +import com.google.idea.blaze.exception.BuildException; + +import javax.annotation.concurrent.Immutable; + +@Immutable +public final class BlazeModException extends BuildException { + public BlazeModException(String message) { + super(message); + } + + public BlazeModException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunner.java b/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunner.java new file mode 100644 index 00000000000..0f2ceccc595 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunner.java @@ -0,0 +1,60 @@ +/* + * 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.command.mod; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.base.bazel.BuildSystem.BuildInvoker; +import com.google.idea.blaze.base.model.ExternalWorkspaceData; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.settings.BuildSystemName; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; + +import java.util.List; + +/** Runs the blaze info command. The results may be cached in the workspace. */ +public abstract class BlazeModRunner { + + public static BlazeModRunner getInstance() { + return ApplicationManager.getApplication().getService(BlazeModRunner.class); + } + + /** + * This calls blaze info without any specific key so blaze info will return all keys and values + * that it has. + * + * @param blazeFlags The blaze flags that will be passed to Blaze. + * @return The blaze info data fields. + */ + public abstract ListenableFuture dumpRepoMapping( + Project project, + BuildInvoker invoker, + BlazeContext context, + BuildSystemName buildSystemName, + List blazeFlags); + + /** + * @param modArgs The arguments passed into `blaze mod ...` + * @param blazeFlags The blaze flags that will be passed to Blaze. + * @return The blaze info value associated with the specified key + */ + protected abstract ListenableFuture runBlazeModGetBytes( + Project project, + BuildInvoker invoker, + BlazeContext context, + List modArgs, + List blazeFlags); +} diff --git a/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunnerImpl.java b/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunnerImpl.java new file mode 100644 index 00000000000..c21cbaf873e --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunnerImpl.java @@ -0,0 +1,76 @@ +package com.google.idea.blaze.base.command.mod; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.idea.blaze.base.async.executor.BlazeExecutor; +import com.google.idea.blaze.base.bazel.BuildSystem; +import com.google.idea.blaze.base.command.BlazeCommand; +import com.google.idea.blaze.base.command.BlazeCommandName; +import com.google.idea.blaze.base.command.BlazeCommandRunner; +import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; +import com.google.idea.blaze.base.model.ExternalWorkspaceData; +import com.google.idea.blaze.base.model.primitives.ExternalWorkspace; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.settings.BuildSystemName; +import com.intellij.openapi.project.Project; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class BlazeModRunnerImpl extends BlazeModRunner { + + @Override + public ListenableFuture dumpRepoMapping( + Project project, + BuildSystem.BuildInvoker invoker, + BlazeContext context, + BuildSystemName buildSystemName, + List blazeFlags) { + return Futures.transform( + runBlazeModGetBytes(project, invoker, context, ImmutableList.of( "dump_repo_mapping", "workspace"), blazeFlags), + bytes -> { + JsonObject json = JsonParser.parseString(new String(bytes, StandardCharsets.UTF_8).trim()).getAsJsonObject(); + + ImmutableList externalWorkspaces = + json + .asMap().entrySet().stream() + .filter(e -> e.getValue().isJsonPrimitive()) + .filter(e -> !e.getValue().getAsString().trim().isEmpty()) + .map(e -> ExternalWorkspace.create(e.getValue().getAsString(), e.getKey())) + .collect(ImmutableList.toImmutableList()); + + return ExternalWorkspaceData.create(externalWorkspaces); + }, + BlazeExecutor.getInstance().getExecutor()); + } + + @Override + public ListenableFuture runBlazeModGetBytes( + Project project, + BuildSystem.BuildInvoker invoker, + BlazeContext context, + List modArgs, + List blazeFlags) { + return BlazeExecutor.getInstance() + .submit(() -> { + BlazeCommand.Builder builder = + BlazeCommand.builder(invoker, BlazeCommandName.MOD) + .addBlazeFlags(blazeFlags); + + if (modArgs != null) { + builder.addBlazeFlags(modArgs); + } + + try (BuildResultHelper buildResultHelper = invoker.createBuildResultHelper()) { + BlazeCommandRunner runner = invoker.getCommandRunner(); + try (InputStream stream = runner.runBlazeMod(project, builder, buildResultHelper, context)) { + return stream.readAllBytes(); + } + } + }); + } +} diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/bazel/FakeBlazeCommandRunner.java b/base/tests/utils/unit/com/google/idea/blaze/base/bazel/FakeBlazeCommandRunner.java index 30ba51cc6ca..e97cb817c96 100644 --- a/base/tests/utils/unit/com/google/idea/blaze/base/bazel/FakeBlazeCommandRunner.java +++ b/base/tests/utils/unit/com/google/idea/blaze/base/bazel/FakeBlazeCommandRunner.java @@ -21,6 +21,7 @@ import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; import com.google.idea.blaze.base.command.info.BlazeInfoException; +import com.google.idea.blaze.base.command.mod.BlazeModException; import com.google.idea.blaze.base.logging.utils.querysync.BuildDepsStatsScope; import com.google.idea.blaze.base.logging.utils.querysync.SyncQueryStatsScope; import com.google.idea.blaze.base.run.testlogs.BlazeTestResults; @@ -29,6 +30,7 @@ import com.google.idea.blaze.base.sync.aspects.BuildResult; import com.google.idea.blaze.exception.BuildException; import com.intellij.openapi.project.Project; + import java.io.InputStream; import java.util.Map; @@ -38,77 +40,88 @@ */ public class FakeBlazeCommandRunner implements BlazeCommandRunner { - @FunctionalInterface - public interface BuildFunction { - BlazeBuildOutputs runBuild(BuildResultHelper buildResultHelper) throws BuildException; - } + @FunctionalInterface + public interface BuildFunction { + BlazeBuildOutputs runBuild(BuildResultHelper buildResultHelper) throws BuildException; + } - private final BuildFunction resultsFunction; - private BlazeCommand command; + private final BuildFunction resultsFunction; + private BlazeCommand command; - public FakeBlazeCommandRunner() { - this( - buildResultHelper -> - BlazeBuildOutputs.fromParsedBepOutput( - BuildResult.SUCCESS, buildResultHelper.getBuildOutput())); - } + public FakeBlazeCommandRunner() { + this( + buildResultHelper -> + BlazeBuildOutputs.fromParsedBepOutput( + BuildResult.SUCCESS, buildResultHelper.getBuildOutput())); + } + + public FakeBlazeCommandRunner(BuildFunction buildFunction) { + this.resultsFunction = buildFunction; + } - public FakeBlazeCommandRunner(BuildFunction buildFunction) { - this.resultsFunction = buildFunction; - } + @Override + public BlazeBuildOutputs run( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context, + Map envVars) + throws BuildException { + command = blazeCommandBuilder.build(); + try { + BlazeBuildOutputs blazeBuildOutputs = resultsFunction.runBuild(buildResultHelper); + int exitCode = blazeBuildOutputs.buildResult.exitCode; + BuildDepsStatsScope.fromContext(context).ifPresent(stats -> stats.setBazelExitCode(exitCode)); + return blazeBuildOutputs; + } catch (GetArtifactsException e) { + return BlazeBuildOutputs.noOutputs(BuildResult.FATAL_ERROR); + } + } - @Override - public BlazeBuildOutputs run( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context, - Map envVars) - throws BuildException { - command = blazeCommandBuilder.build(); - try { - BlazeBuildOutputs blazeBuildOutputs = resultsFunction.runBuild(buildResultHelper); - int exitCode = blazeBuildOutputs.buildResult.exitCode; - BuildDepsStatsScope.fromContext(context).ifPresent(stats -> stats.setBazelExitCode(exitCode)); - return blazeBuildOutputs; - } catch (GetArtifactsException e) { - return BlazeBuildOutputs.noOutputs(BuildResult.FATAL_ERROR); + @Override + public BlazeTestResults runTest( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context, + Map envVars) { + return BlazeTestResults.NO_RESULTS; } - } - @Override - public BlazeTestResults runTest( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context, - Map envVars) { - return BlazeTestResults.NO_RESULTS; - } + @Override + public InputStream runQuery( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context) + throws BuildException { + SyncQueryStatsScope.fromContext(context).ifPresent(stats -> stats.setBazelExitCode(0)); + return InputStream.nullInputStream(); + } - @Override - public InputStream runQuery( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context) - throws BuildException { - SyncQueryStatsScope.fromContext(context).ifPresent(stats -> stats.setBazelExitCode(0)); - return InputStream.nullInputStream(); - } + @Override + @MustBeClosed + public InputStream runBlazeInfo( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context) + throws BlazeInfoException { + return InputStream.nullInputStream(); + } - @Override - @MustBeClosed - public InputStream runBlazeInfo( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context) - throws BlazeInfoException { - return InputStream.nullInputStream(); - } + @Override + @MustBeClosed + public InputStream runBlazeMod( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context) + throws BlazeModException { + return InputStream.nullInputStream(); + } - public BlazeCommand getIssuedCommand() { - return command; - } + public BlazeCommand getIssuedCommand() { + return command; + } }