diff --git a/base/BUILD b/base/BUILD index c39b2dd87a7..4f27f7343e2 100644 --- a/base/BUILD +++ b/base/BUILD @@ -45,6 +45,7 @@ java_library( "//shared:vcs", "//third_party/auto_value", "@error_prone_annotations//jar", + "@gson//jar" ], ) 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..2c8d3e84ccf 100644 --- a/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java +++ b/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 The Bazel Authors. All rights reserved. + * Copyright 2024 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. @@ -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..3ccd419208e 100644 --- a/base/src/com/google/idea/blaze/base/command/BlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/BlazeCommandRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Bazel Authors. All rights reserved. + * Copyright 2024 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. @@ -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..d689fafd8cd 100644 --- a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Bazel Authors. All rights reserved. + * Copyright 2024 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. @@ -27,6 +27,7 @@ import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; import com.google.idea.blaze.base.command.buildresult.BuildResultHelperBep; import com.google.idea.blaze.base.command.buildresult.ParsedBepOutput; +import com.google.idea.blaze.base.command.mod.BlazeModException; import com.google.idea.blaze.base.console.BlazeConsoleLineProcessorProvider; import com.google.idea.blaze.base.execution.BazelGuard; import com.google.idea.blaze.base.execution.ExecutionDeniedException; @@ -54,6 +55,7 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Map; import java.util.Optional; @@ -64,11 +66,11 @@ public class CommandLineBlazeCommandRunner implements BlazeCommandRunner { @Override public BlazeBuildOutputs run( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context, - Map envVars) { + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context, + Map envVars) { try { performGuardCheck(project, context); } catch (ExecutionDeniedException e) { @@ -99,7 +101,7 @@ public BlazeBuildOutputs run( ParsedBepOutput buildOutput = buildResultHelper.getBuildOutput(stringInterner); context.output(SummaryOutput.output(SummaryOutput.Prefix.TIMESTAMP, "Handling parsed BEP outputs...")); BlazeBuildOutputs blazeBuildOutputs = BlazeBuildOutputs.fromParsedBepOutput( - buildResult, buildOutput); + buildResult, buildOutput); context.output(SummaryOutput.output(SummaryOutput.Prefix.TIMESTAMP, "BEP outputs have been processed.")); return blazeBuildOutputs; } catch (GetArtifactsException e) { @@ -123,7 +125,7 @@ public BlazeTestResults runTest( } // For tests, we have to pass the environment variables as `--test_env`, otherwise they don't get forwarded - for (Map.Entry env: envVars.entrySet()) { + for (Map.Entry env : envVars.entrySet()) { blazeCommandBuilder.addBlazeFlags(BlazeFlags.TEST_ENV, String.format("%s=%s", env.getKey(), env.getValue())); } @@ -224,6 +226,45 @@ public InputStream runBlazeInfo( } } + @Override + @MustBeClosed + public InputStream runBlazeMod( + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context) + throws BuildException { + performGuardCheckAsBuildException(project, context); + + if (project.getBasePath() == null) { + throw new BlazeModException("Project base path is null"); + } + + try (Closer closer = Closer.create()) { + Path queriesDir = Files.createDirectories(Paths.get(project.getBasePath()).resolve("queries")); + Path tmpFile = Files.createTempFile(queriesDir, "blaze-mod-", ".stdout"); + + OutputStream stdout = 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(stdout) + .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 BlazeModException("io error while running blaze mod", 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..8c1f565fd33 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/mod/BlazeModException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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.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..cf3a27f8cb5 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunner.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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 {@code blaze mod ...} 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 {@code blaze mod dump_repo_mapping workspace} so blaze mod will return all mapped + * repos visible to the current workspace. + * + * @param flags The blaze flags that will be passed to Blaze. + * @return a ListenableFuture + */ + @SuppressWarnings({"unused"}) // will be used shortly + public abstract ListenableFuture dumpRepoMapping( + Project project, + BuildInvoker invoker, + BlazeContext context, + BuildSystemName buildSystemName, + List flags); + + /** + * @param args The arguments passed into `blaze mod ...` + * @param flags The blaze flags that will be passed to {@code blaze ...} + * @return the stdout bytes of the command + */ + protected abstract ListenableFuture runBlazeModGetBytes( + Project project, + BuildInvoker invoker, + BlazeContext context, + List args, + List flags); +} 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..12622f271ef --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/mod/BlazeModRunnerImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 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.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 flags) { + return Futures.transform( + runBlazeModGetBytes(project, invoker, context, ImmutableList.of( "dump_repo_mapping", "workspace"), flags), + bytes -> { + JsonObject json = JsonParser.parseString(new String(bytes, StandardCharsets.UTF_8).trim()).getAsJsonObject(); + + ImmutableList externalWorkspaces = + json.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 args, + List flags) { + return BlazeExecutor.getInstance() + .submit(() -> { + BlazeCommand.Builder builder = + BlazeCommand.builder(invoker, BlazeCommandName.MOD) + .addBlazeFlags(flags); + + if (args != null) { + builder.addBlazeFlags(args); + } + + 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..41157d0fb2f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Bazel Authors. All rights reserved. + * Copyright 2024 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. @@ -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; @@ -59,11 +61,11 @@ public FakeBlazeCommandRunner(BuildFunction buildFunction) { @Override public BlazeBuildOutputs run( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context, - Map envVars) + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context, + Map envVars) throws BuildException { command = blazeCommandBuilder.build(); try { @@ -78,11 +80,11 @@ public BlazeBuildOutputs run( @Override public BlazeTestResults runTest( - Project project, - BlazeCommand.Builder blazeCommandBuilder, - BuildResultHelper buildResultHelper, - BlazeContext context, - Map envVars) { + Project project, + BlazeCommand.Builder blazeCommandBuilder, + BuildResultHelper buildResultHelper, + BlazeContext context, + Map envVars) { return BlazeTestResults.NO_RESULTS; } @@ -108,6 +110,17 @@ public InputStream runBlazeInfo( 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; }