diff --git a/.bazelrc b/.bazelrc index d8f1bc891d8..2c602b177ce 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,5 @@ build --java_language_version=17 --java_runtime_version=17 +build --tool_java_language_version=17 --tool_java_runtime_version=17 # Delete test data packages, needed for bazel integration tests. Update by running the following command: # bazel run @rules_bazel_integration_test//tools:update_deleted_packages diff --git a/aswb/aswb.bazelproject b/aswb/aswb.bazelproject index 96d6a6a7271..015b3e045d8 100644 --- a/aswb/aswb.bazelproject +++ b/aswb/aswb.bazelproject @@ -22,3 +22,7 @@ test_sources: */testcompat/unittests* */testcompat/integrationtests* */testcompat/utils/integration* + +additional_languages: + kotlin + diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java index 88c62b3ee64..f4bbf2162f2 100644 --- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java +++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java @@ -17,6 +17,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.AndroidDeployInfo; import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.Artifact; import com.google.idea.blaze.android.manifest.ManifestParser.ParsedManifest; @@ -29,6 +30,7 @@ import com.google.idea.blaze.common.artifact.OutputArtifact; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.util.containers.ContainerUtil; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -45,7 +47,7 @@ public class BlazeApkDeployInfoProtoHelper { public AndroidDeployInfo readDeployInfoProtoForTarget( Label target, BuildResultHelper buildResultHelper, Predicate pathFilter) throws GetDeployInfoException { - ImmutableList outputArtifacts; + ImmutableSet outputArtifacts; try { outputArtifacts = buildResultHelper.getBuildArtifactsForTarget(target, pathFilter); } catch (GetArtifactsException e) { @@ -62,7 +64,7 @@ public AndroidDeployInfo readDeployInfoProtoForTarget( log.warn(outputArtifact.getRelativePath() + " -> " + outputArtifact.getRelativePath()); } log.warn("All local artifacts for " + target + ":"); - List allBuildArtifacts = + ImmutableSet allBuildArtifacts = buildResultHelper.getBuildArtifactsForTarget(target, path -> true); List allLocalFiles = LocalFileArtifact.getLocalFiles(allBuildArtifacts); for (File file : allLocalFiles) { @@ -87,7 +89,7 @@ public AndroidDeployInfo readDeployInfoProtoForTarget( .collect(Collectors.joining(", ", "[", "]"))); } - try (InputStream inputStream = outputArtifacts.get(0).getInputStream()) { + try (InputStream inputStream = ContainerUtil.getFirstItem(outputArtifacts).getInputStream()) { return AndroidDeployInfo.parseFrom(inputStream); } catch (IOException e) { throw new GetDeployInfoException(e.getMessage()); diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java index d057bc54172..f505e849402 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java @@ -26,8 +26,6 @@ import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.command.BlazeFlags; 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.buildresult.BuildResultHelperBep; import com.google.idea.blaze.base.filecache.FileCaches; import com.google.idea.blaze.base.ideinfo.AndroidInstrumentationInfo; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; @@ -195,7 +193,7 @@ public void run(@NotNull BlazeLaunchContext launchContext) String.format("Starting %s test...\n", Blaze.buildSystemName(project))); int retVal; - try (BuildResultHelper buildResultHelper = new BuildResultHelperBep()) { + try (final var buildResultHelper = new BuildResultHelper()) { commandBuilder.addBlazeFlags(buildResultHelper.getBuildFlags()); BlazeCommand command = commandBuilder.build(); ExecutionUtils.println(console, command + "\n"); @@ -212,16 +210,13 @@ public void run(@NotNull BlazeLaunchContext launchContext) if (retVal != 0) { context.setHasError(); } else { - testResultsHolder.setTestResults( - buildResultHelper.getTestResults(Optional.empty())); + testResultsHolder.setTestResults(buildResultHelper.getTestResults()); } ListenableFuture unusedFuture = FileCaches.refresh( project, context, BlazeBuildOutputs.noOutputs(BuildResult.fromExitCode(retVal))); - } catch (GetArtifactsException e) { - LOG.error(e.getMessage()); } return !context.hasErrors(); })); diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java index e945650f37d..8e323a36657 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java @@ -80,7 +80,7 @@ private static void notifyMissingPlugin(BlazeContext context, PluginNameAndId pl + "Click here to install/enable it, then restart the IDE", plugin.name); IssueOutput.error(msg) - .navigatable(PluginUtils.installOrEnablePluginNavigable(plugin.id)) + .withNavigatable(PluginUtils.installOrEnablePluginNavigable(plugin.id)) .submit(context); } } diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java index 62a41bf8ba5..999a810506e 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java +++ b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java @@ -41,10 +41,10 @@ import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.output.IssueOutput.Category; import com.google.idea.blaze.base.scope.output.PerformanceWarning; import com.google.idea.blaze.common.Output; import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.build.events.MessageEvent.Kind; import com.intellij.openapi.project.Project; import java.util.Collection; import java.util.Collections; @@ -327,7 +327,7 @@ private ImmutableList buildAndroidResourceModules( if (mergeResourcesEnabled.getValue()) { messageBuilder.append(" ").append("Merging Resources...").append("\n"); String message = messageBuilder.toString(); - context.accept(IssueOutput.issue(Category.INFORMATION, message).build()); + context.accept(IssueOutput.issue(Kind.INFO, message).build()); result.add(mergeAndroidResourceModules(androidResourceModulesWithJavaPackage)); } else { diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java index 1b2ab3540f9..8e806f54bd2 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java +++ b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java @@ -39,6 +39,7 @@ import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter; import com.google.idea.blaze.common.Output; import com.google.idea.blaze.java.sync.model.BlazeJarLibrary; +import com.intellij.build.events.MessageEvent.Kind; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import java.util.Collection; @@ -110,8 +111,7 @@ static Consumer asConsumer(BlazeContext context) { context.output(issue); if (issue instanceof IssueOutput) { IssueOutput issueOutput = (IssueOutput) issue; - if (issueOutput.getCategory() - == com.google.idea.blaze.base.scope.output.IssueOutput.Category.ERROR) { + if (issueOutput.getKind() == Kind.ERROR) { context.setHasError(); } } diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/problems/GeneratedResourceWarnings.java b/aswb/src/com/google/idea/blaze/android/sync/importer/problems/GeneratedResourceWarnings.java index 81641227959..7edd9de643c 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/importer/problems/GeneratedResourceWarnings.java +++ b/aswb/src/com/google/idea/blaze/android/sync/importer/problems/GeneratedResourceWarnings.java @@ -71,9 +71,7 @@ public static void submit( + "Double-click to add to project view if needed to resolve" + " references.", interestingDirectories.size())) - .inFile(projectViewFile) - .onLine(1) - .inColumn(1) + .withFile(projectViewFile) .build()); for (Map.Entry entry : interestingDirectories.entrySet()) { context.accept( @@ -81,10 +79,7 @@ public static void submit( String.format( "Dropping generated resource directory '%s' w/ %d subdirs", entry.getKey(), entry.getValue())) - .inFile(projectViewFile) - .navigatable( - new AddGeneratedResourceDirectoryNavigatable( - project, projectViewFile, entry.getKey())) + .withFile(projectViewFile) .build()); } } @@ -98,7 +93,7 @@ public static void submit( unusedAllowlistEntries.size(), GeneratedAndroidResourcesSection.KEY.getName(), String.join("\n ", unusedAllowlistEntries))) - .inFile(projectViewFile) + .withFile(projectViewFile) .build()); } } diff --git a/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java b/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java index 7301a0080ab..9445ccc47c1 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java +++ b/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java @@ -51,7 +51,7 @@ public static AndroidSdkPlatform getAndroidSdkPlatform( if (sdks.isEmpty()) { String msg = "No Android SDK configured. Please use the SDK manager to configure."; IssueOutput.error(msg) - .navigatable( + .withNavigatable( new Navigatable() { @Override public void navigate(boolean b) { @@ -87,7 +87,7 @@ public boolean canNavigateToSource() { + getAvailableTargetHashesAsList(sdks) + ". To install more android SDKs, use the SDK manager."; IssueOutput.error(msg) - .inFile(projectViewFile != null ? projectViewFile.projectViewFile : null) + .withFile(projectViewFile != null ? projectViewFile.projectViewFile : null) .submit(context); BlazeSyncManager.printAndLogError(msg, context); return null; @@ -98,7 +98,7 @@ public boolean canNavigateToSource() { ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile(); String msg = String.format(NO_SDK_ERROR_TEMPLATE, androidSdk, getAllAvailableTargetHashes()); IssueOutput.error(msg) - .inFile(projectViewFile != null ? projectViewFile.projectViewFile : null) + .withFile(projectViewFile != null ? projectViewFile.projectViewFile : null) .submit(context); BlazeSyncManager.printAndLogError(msg, context); return null; diff --git a/base/BUILD b/base/BUILD index a2a8d393c37..b06581ceec8 100644 --- a/base/BUILD +++ b/base/BUILD @@ -17,12 +17,13 @@ load( "intellij_integration_test_suite", "intellij_unit_test_suite", ) +load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") -java_library( +kt_jvm_library( name = "base", - srcs = glob(["src/**/*.java"]), - javacopts = ["-Xep:FutureReturnValueIgnored:OFF"], + srcs = glob(["src/**/*.java", "src/**/*.kt"]), resources = glob(["src/resources/**/*"]), + resource_strip_prefix = "base/src", visibility = PLUGIN_PACKAGES_VISIBILITY, deps = [ "//common/actions", diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml index 34ad207f953..f334fc5a205 100644 --- a/base/src/META-INF/blaze-base.xml +++ b/base/src/META-INF/blaze-base.xml @@ -408,6 +408,9 @@ + @@ -587,6 +590,7 @@ + @@ -612,7 +616,6 @@ - @@ -680,7 +683,9 @@ - + + + diff --git a/base/src/com/google/idea/blaze/base/async/executor/ProgressIndicatorStub.kt b/base/src/com/google/idea/blaze/base/async/executor/ProgressIndicatorStub.kt new file mode 100644 index 00000000000..fa24de52e9a --- /dev/null +++ b/base/src/com/google/idea/blaze/base/async/executor/ProgressIndicatorStub.kt @@ -0,0 +1,62 @@ +package com.google.idea.blaze.base.async.executor + +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.TaskInfo +import com.intellij.openapi.wm.ex.ProgressIndicatorEx + +object ProgressIndicatorStub : ProgressIndicatorEx { + override fun start() { } + + override fun stop() { } + + override fun isRunning(): Boolean { return false } + + override fun cancel() { } + + override fun isCanceled(): Boolean { return false } + + override fun setText(p0: String?) { } + + override fun getText(): String { return "" } + + override fun setText2(p0: String?) { } + + override fun getText2(): String { return "" } + + override fun getFraction(): Double { return 0.0 } + + override fun setFraction(p0: Double) { } + + override fun pushState() { } + + override fun popState() { } + + override fun isModal(): Boolean { return false } + + override fun getModalityState(): ModalityState { return ModalityState.NON_MODAL /* required for backwards compatability */ } + + override fun setModalityProgress(p0: ProgressIndicator?) { } + + override fun isIndeterminate(): Boolean { return false; } + + override fun setIndeterminate(p0: Boolean) { } + + override fun checkCanceled() { } + + override fun isPopupWasShown(): Boolean { return false; } + + override fun isShowing(): Boolean { return false; } + + override fun addStateDelegate(p0: ProgressIndicatorEx) { } + + override fun finish(p0: TaskInfo) { } + + override fun isFinished(p0: TaskInfo): Boolean { return false; } + + override fun wasStarted(): Boolean { return false; } + + override fun processFinish() { } + + override fun initStateFrom(p0: ProgressIndicator) { } +} \ No newline at end of file diff --git a/base/src/com/google/idea/blaze/base/async/executor/ProgressiveTaskWithProgressIndicator.java b/base/src/com/google/idea/blaze/base/async/executor/ProgressiveTaskWithProgressIndicator.java index f7b0c15f8bd..8511e931108 100644 --- a/base/src/com/google/idea/blaze/base/async/executor/ProgressiveTaskWithProgressIndicator.java +++ b/base/src/com/google/idea/blaze/base/async/executor/ProgressiveTaskWithProgressIndicator.java @@ -40,7 +40,8 @@ public class ProgressiveTaskWithProgressIndicator { public enum Modality { MODAL, // This task must start in the foreground and stay there. BACKGROUNDABLE, // This task will start in the foreground, but can be sent to the background. - ALWAYS_BACKGROUND // This task will start in the background and stay there. + ALWAYS_BACKGROUND, // This task will start in the background and stay there. + BUILD_VIEW // Progress of this task is tracked in the build view } @Nullable private final Project project; @@ -104,6 +105,10 @@ public void submitTaskLater(Progressive progressive) { * progress dialog. */ public ListenableFuture submitTaskWithResult(ProgressiveWithResult progressive) { + if (modality == Modality.BUILD_VIEW) { + return executor.submit(() -> progressive.compute(ProgressIndicatorStub.INSTANCE)); + } + // The progress indicator must be created on the UI thread. final ProgressWindow indicator = UIUtil.invokeAndWaitIfNeeded( 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 5e76138a86c..52b9900d82c 100644 --- a/base/src/com/google/idea/blaze/base/bazel/AbstractBuildInvoker.java +++ b/base/src/com/google/idea/blaze/base/bazel/AbstractBuildInvoker.java @@ -26,7 +26,6 @@ import com.google.idea.blaze.base.command.BlazeFlags; import com.google.idea.blaze.base.command.BlazeInvocationContext; 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; @@ -95,7 +94,7 @@ public boolean supportsParallelism() { @Override @MustBeClosed public BuildResultHelper createBuildResultHelper() { - return new BuildResultHelperBep(); + return new BuildResultHelper(); } @Override diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystem.java b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystem.java index 1fa124ce963..87d78f74eb4 100644 --- a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystem.java +++ b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystem.java @@ -15,11 +15,8 @@ */ package com.google.idea.blaze.base.bazel; -import com.google.errorprone.annotations.MustBeClosed; import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.command.CommandLineBlazeCommandRunner; -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.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.Kind; @@ -52,12 +49,6 @@ public BazelInvoker(Project project, BlazeContext blazeContext, String path) { BazelBuildSystem.this, new CommandLineBlazeCommandRunner()); } - - @Override - @MustBeClosed - public BuildResultHelper createBuildResultHelper() { - return new BuildResultHelperBep(); - } } @Override diff --git a/base/src/com/google/idea/blaze/base/buildview/BazelService.kt b/base/src/com/google/idea/blaze/base/buildview/BazelService.kt new file mode 100644 index 00000000000..a39d216d25a --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/BazelService.kt @@ -0,0 +1,173 @@ +package com.google.idea.blaze.base.buildview + +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent +import com.google.idea.blaze.base.buildview.events.BuildEventParser +import com.google.idea.blaze.base.command.BlazeCommand +import com.google.idea.blaze.base.command.BlazeCommandName +import com.google.idea.blaze.base.command.buildresult.BuildResultHelper +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot +import com.google.idea.blaze.base.scope.BlazeContext +import com.google.idea.blaze.base.sync.aspects.BlazeBuildOutputs +import com.google.idea.blaze.base.sync.aspects.BuildResult +import com.google.protobuf.CodedInputStream +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.OSProcessHandler +import com.intellij.execution.process.ProcessEvent +import com.intellij.execution.process.ProcessListener +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.util.io.LimitedInputStream +import com.intellij.util.ui.EDT +import kotlinx.coroutines.* +import java.io.BufferedInputStream +import java.io.FileInputStream +import kotlin.io.path.pathString + +private val LOG: Logger = Logger.getInstance(BazelService::class.java) + +@Service(Service.Level.PROJECT) +class BazelService(private val project: Project) : Disposable { + companion object { + @JvmStatic + fun instance(project: Project): BazelService = project.service() + } + + // #api223 use the injected scope + private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + override fun dispose() { + scope.cancel() + } + + private fun assertNonBlocking() { + LOG.assertTrue( + !EDT.isCurrentThreadEdt(), + "action would block UI thread", + ) + LOG.assertTrue( + !ApplicationManager.getApplication().isReadAccessAllowed, + "action holds read lock, can block UI thread" + ) + } + + private fun executionScope( + ctx: BlazeContext, + block: suspend CoroutineScope.(BuildResultHelper) -> T + ): T { + return ctx.pushJob(scope) { + BuildResultHelper().use { block(it) } + } + } + + private suspend fun execute(ctx: BlazeContext, cmd: BlazeCommand): Int { + val root = cmd.effectiveWorkspaceRoot.orElseGet { WorkspaceRoot.fromProject(project).path() } + + val handler = GeneralCommandLine() + .withExePath(cmd.binaryPath) + .withParameters(cmd.toArgumentList()) + .apply { setWorkDirectory(root.pathString) } // required for backwards compatability + .withRedirectErrorStream(true) + .let(::OSProcessHandler) + + handler.addProcessListener(object : ProcessListener { + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { + ctx.println(event.text.trimEnd()) + } + }) + handler.startNotify() + + try { + while (!handler.isProcessTerminated) delay(100) + } finally { + handler.destroyProcess() + } + + val exitCode = handler.exitCode ?: 1 + if (exitCode != 0) { + ctx.setHasError() + } + + return exitCode + } + + private suspend fun parseEvent(ctx: BlazeContext, stream: BufferedInputStream) { + // make sure that there are at least four bytes already available + while (stream.available() < 4) delay(10) + + // protobuf messages are delimited by size (encoded as varint32), + // read size manually to ensure the entire message is already available + val size = CodedInputStream.readRawVarint32(stream.read(), stream) + while (stream.available() < size) delay(10) + + val eventStream = LimitedInputStream(stream, size) + val event = try { + BuildEvent.parseFrom(eventStream) + } catch (e: Exception) { + LOG.error("could not parse event", e) + + // if the message could not be parsed, make sure to skip it + if (eventStream.bytesRead < size) { + stream.skip(size.toLong() - eventStream.bytesRead) + } + + return + } + + if (event == null) { + delay(10) + } else { + BuildEventParser.parse(event)?.let(ctx::output) + } + } + + private fun CoroutineScope.parseEvents(ctx: BlazeContext, helper: BuildResultHelper): Job { + return launch(CoroutineName("EventParser")) { + try { + // wait for bazel to create the output file + while (!helper.outputFile.exists()) delay(10) + + FileInputStream(helper.outputFile).buffered().use { stream -> + // keep reading events while the coroutine is active, i.e. bazel is still running, + // or while the stream has data available (to ensure that all events are processed) + while (isActive || stream.available() > 0) { + parseEvent(ctx, stream) + } + } + } + catch (e: CancellationException) { + throw e + } + catch (e: Exception) { + LOG.error("error in event parser", e) + } + } + } + + fun build(ctx: BlazeContext, cmdBuilder: BlazeCommand.Builder): BlazeBuildOutputs { + assertNonBlocking() + LOG.assertTrue(cmdBuilder.name == BlazeCommandName.BUILD) + + return executionScope(ctx) { provider -> + cmdBuilder.addBlazeFlags(provider.getBuildFlags()) + + val parseJob = parseEvents(ctx, provider) + + val exitCode = execute(ctx, cmdBuilder.build()) + val result = BuildResult.fromExitCode(exitCode) + + parseJob.cancelAndJoin() + + if (result.status != BuildResult.Status.SUCCESS) { + BlazeBuildOutputs.noOutputs(result) + } else { + BlazeBuildOutputs.fromParsedBepOutput(result, provider.getBuildOutput()) + } + } + } +} + diff --git a/base/src/com/google/idea/blaze/base/buildview/BuildViewMigration.kt b/base/src/com/google/idea/blaze/base/buildview/BuildViewMigration.kt new file mode 100644 index 00000000000..34796e2ea92 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/BuildViewMigration.kt @@ -0,0 +1,24 @@ +package com.google.idea.blaze.base.buildview + +import com.google.idea.blaze.base.async.executor.ProgressiveTaskWithProgressIndicator +import com.google.idea.blaze.base.scope.BlazeContext +import com.intellij.openapi.util.registry.Registry + +object BuildViewMigration { + @JvmStatic + val enabled get(): Boolean = Registry.`is`("bazel.new.sync.view") + + @JvmStatic + fun present(ctx: BlazeContext): Boolean { + return ctx.getScope(BuildViewScope::class.java) != null + } + + @JvmStatic + fun progressModality(): ProgressiveTaskWithProgressIndicator.Modality { + return if (enabled) { + ProgressiveTaskWithProgressIndicator.Modality.BUILD_VIEW + } else { + ProgressiveTaskWithProgressIndicator.Modality.ALWAYS_BACKGROUND + } + } +} \ No newline at end of file diff --git a/base/src/com/google/idea/blaze/base/buildview/BuildViewScope.kt b/base/src/com/google/idea/blaze/base/buildview/BuildViewScope.kt new file mode 100644 index 00000000000..41f4c741ee1 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/BuildViewScope.kt @@ -0,0 +1,106 @@ +@file:Suppress("UnstableApiUsage") + +package com.google.idea.blaze.base.buildview + +import com.google.idea.blaze.base.scope.BlazeContext +import com.google.idea.blaze.base.scope.BlazeScope +import com.google.idea.blaze.base.scope.OutputSink +import com.google.idea.blaze.base.scope.output.IssueOutput +import com.google.idea.blaze.base.scope.output.StatusOutput +import com.google.idea.blaze.common.Output +import com.google.idea.blaze.common.PrintOutput +import com.intellij.build.BuildDescriptor +import com.intellij.build.DefaultBuildDescriptor +import com.intellij.build.SyncViewManager +import com.intellij.build.progress.BuildProgress +import com.intellij.build.progress.BuildProgressDescriptor +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator +import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase +import com.intellij.openapi.project.Project +import java.util.function.Consumer + +class BuildViewScope(project: Project, private val title: String) : BlazeScope { + companion object { + @JvmStatic + fun of(ctx: BlazeContext): BuildViewScope? = ctx.getScope(BuildViewScope::class.java) + } + + private var progress = SyncViewManager.createBuildProgress(project) + private var indicator = BackgroundableProcessIndicator(project, title, "Cancel", "Cancel", true) + + override fun onScopeBegin(ctx: BlazeContext) { + progress.start(ProgressDescriptor(title, ctx)) + + indicator.addStateDelegate(object : AbstractProgressIndicatorExBase() { + override fun cancel() { + super.cancel() + ctx.setCancelled() + } + }) + indicator.start() + + addOutputSink(ctx) { + progress.output(it.text + '\n', true) + } + addOutputSink(ctx) { + progress.output(it.status + '\n', true) + } + addOutputSink(ctx) { + progress.buildIssue(it, it.kind) + } + } + + private inline fun addOutputSink(ctx: BlazeContext, consumer: Consumer) { + ctx.addOutputSink(T::class.java) { it: T -> + consumer.accept(it) + OutputSink.Propagation.Stop + } + } + + override fun onScopeEnd(ctx: BlazeContext) { + val progress = this.progress ?: return + + when { + ctx.isCancelled -> progress.cancel() + ctx.hasErrors() -> progress.fail() + else -> progress.finish() + } + + indicator.stop() + indicator.processFinish() + } + + fun startProgress(title: String): BuildProgress? { + indicator.text2 = title + return progress?.progress(title) + } +} + +private class ProgressDescriptor(private val title: String, ctx: BlazeContext) : + BuildProgressDescriptor { + private val descriptor = DefaultBuildDescriptor(Any(), title, "", System.currentTimeMillis()) + .withRestartAction(RestartAction(ctx)) + + override fun getTitle(): String = title + + override fun getBuildDescriptor(): BuildDescriptor = descriptor +} + +private class RestartAction(private val ctx: BlazeContext) : + AnAction({ "Stop" }, AllIcons.Actions.Suspend) { + override fun actionPerformed(event: AnActionEvent) { + ctx.setCancelled() + } + + override fun update(event: AnActionEvent) { + event.presentation.isEnabled = !ctx.isCancelled && !ctx.isEnding + } + + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.BGT + } +} diff --git a/base/src/com/google/idea/blaze/base/buildview/ContextExt.kt b/base/src/com/google/idea/blaze/base/buildview/ContextExt.kt new file mode 100644 index 00000000000..a5d615348c1 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/ContextExt.kt @@ -0,0 +1,27 @@ +package com.google.idea.blaze.base.buildview + +import com.google.idea.blaze.base.scope.BlazeContext +import com.google.idea.blaze.common.PrintOutput +import com.intellij.openapi.progress.runBlockingMaybeCancellable +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async + +fun BlazeContext.println(msg: String) { + output(PrintOutput(msg)) +} + +fun BlazeContext.pushJob( + scope: CoroutineScope, + name: String = "BazelContext", + block: suspend CoroutineScope.() -> T, +): T { + val deferred = scope.async(Dispatchers.IO + CoroutineName(name)) { block() } + + addCancellationHandler { deferred.cancel() } + + return runBlockingMaybeCancellable { + deferred.await() + } +} \ No newline at end of file diff --git a/base/src/com/google/idea/blaze/base/buildview/events/AbortedParser.kt b/base/src/com/google/idea/blaze/base/buildview/events/AbortedParser.kt new file mode 100644 index 00000000000..f2250fc29e0 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/events/AbortedParser.kt @@ -0,0 +1,75 @@ +package com.google.idea.blaze.base.buildview.events + +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId +import com.google.idea.blaze.base.scope.output.IssueOutput +import com.intellij.build.events.MessageEvent + +private fun reportMissingImplementation(id: BuildEventId): T? { + LOG.error("missing implementation: $id") + return null +} + +class AbortedParser : BuildEventParser { + private fun getDescription(id: BuildEventId): String? { + return when { + id.hasUnconfiguredLabel() -> "could not find label: ${id.unconfiguredLabel.label} - make sure a label or a file with that name exists" + id.hasTargetConfigured() -> "could not configure target: ${id.targetConfigured.label}" + id.hasTargetCompleted() -> "could not complete target: ${id.targetCompleted.label}" + id.hasConfiguredLabel() -> null + else -> reportMissingImplementation(id) + } + } + + private fun getChildDescription(id: BuildEventId): String? { + return when { + id.hasUnconfiguredLabel() -> "depends on undefined label: ${id.unconfiguredLabel.label} - might not be a direct dependency" + id.hasConfiguredLabel() -> null + else -> reportMissingImplementation(id) + } + } + + private fun buildDescription(event: BuildEvent): String? { + val builder = StringBuilder() + + if (event.aborted.description.isNotBlank()) { + builder.append(event.aborted.description) + } else { + builder.append(getDescription(event.id) ?: return null) + } + builder.append("\n\n") + + for (child in event.childrenList) { + val description = getChildDescription(child) ?: return null + + builder.append(description) + builder.append("\n") + } + + return builder.toString().trimEnd() + } + + private fun getLabel(id: BuildEventId): String? { + return when { + id.hasTargetConfigured() -> id.targetConfigured.label + id.hasTargetCompleted() -> id.targetCompleted.label + id.hasUnconfiguredLabel() -> id.unconfiguredLabel.label + id.hasConfiguredLabel() -> id.configuredLabel.label + id.hasUnstructuredCommandLine() -> null + else -> reportMissingImplementation(id) + } + } + + override fun parse(event: BuildEvent): IssueOutput? { + if (!event.hasAborted()) return null + + val label = getLabel(event.id) ?: return null + val issue = BazelBuildIssue( + label = label, + title = "${event.aborted.reason}: $label", + description = buildDescription(event) ?: return null, + ) + + return IssueOutput(issue, MessageEvent.Kind.ERROR) + } +} \ No newline at end of file diff --git a/base/src/com/google/idea/blaze/base/buildview/events/ActionCompletedParser.kt b/base/src/com/google/idea/blaze/base/buildview/events/ActionCompletedParser.kt new file mode 100644 index 00000000000..f73e0affa24 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/events/ActionCompletedParser.kt @@ -0,0 +1,66 @@ +package com.google.idea.blaze.base.buildview.events + +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.* +import com.google.idea.blaze.base.scope.output.IssueOutput +import com.intellij.build.events.MessageEvent +import java.io.IOException +import java.net.URI +import java.nio.file.Files +import java.nio.file.Path + +class ActionCompletedParser : BuildEventParser { + private fun getDescription(body: ActionExecuted): String? { + if (!body.hasStderr()) { + if (body.hasFailureDetail()) { + return body.failureDetail.message + } + + return null + } + + val uri = try { + URI.create(body.stderr.uri) + } catch (e: IllegalArgumentException) { + return "Invalid output URI: ${body.stderr.uri}" + } + + return try { + Files.readString(Path.of(uri)) + } catch (e: IOException) { + "Could not read output file: ${e.message}" + } + } + + override fun parse(event: BuildEvent): IssueOutput? { + if (!event.id.hasActionCompleted()) return null + val id = event.id.actionCompleted + + val isExternal = !id.label.startsWith("//") + + if (!event.hasAction()) return null + val body = event.action + + val isWarning = !body.hasFailureDetail() + + // ignore warnings from external projects + if (isExternal && isWarning) return null + + val name = if (isWarning) { + "BUILD_WARNING" + } else { + "BUILD_FAILURE" + } + + val issue = BazelBuildIssue( + label = id.label, + title = "$name: ${id.label}", + description = getDescription(body) ?: return null, + ) + + // TODO: if this is reused for a build view, this logic needs to be adjusted + return IssueOutput( + issue, + if (isWarning) MessageEvent.Kind.INFO else MessageEvent.Kind.WARNING, + ) + } +} diff --git a/base/src/com/google/idea/blaze/base/buildview/events/BazelBuildIssue.kt b/base/src/com/google/idea/blaze/base/buildview/events/BazelBuildIssue.kt new file mode 100644 index 00000000000..011fb1289eb --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/events/BazelBuildIssue.kt @@ -0,0 +1,37 @@ +@file:Suppress("UnstableApiUsage") + +package com.google.idea.blaze.base.buildview.events + +import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager +import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils +import com.google.idea.sdkcompat.general.FileNavigatableCompat +import com.intellij.build.issue.BuildIssue +import com.intellij.build.issue.BuildIssueQuickFix +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.project.Project +import com.intellij.pom.Navigatable + +data class BazelBuildIssue( + override val title: String, + override val description: String, + override val quickFixes: List = emptyList(), + val label: String? = null, +) : BuildIssue { + + inner class LabelNavigatable(private val project: Project) : FileNavigatableCompat() { + override fun findFileAsync(): OpenFileDescriptor? { + // TODO: better use `bazel query "label" --output=location` - would be way more accurate ?? + + val label = LabelUtils.createLabelFromString(null, label) ?: return null + val element = BuildReferenceManager.getInstance(project).resolveLabel(label) ?: return null + + val file = element.containingFile.virtualFile ?: return null + return OpenFileDescriptor(project, file, element.textRange.startOffset) + } + } + + override fun getNavigatable(project: Project): Navigatable? { + if (label == null) return null + return LabelNavigatable(project) + } +} \ No newline at end of file diff --git a/base/src/com/google/idea/blaze/base/buildview/events/BuildEventParser.kt b/base/src/com/google/idea/blaze/base/buildview/events/BuildEventParser.kt new file mode 100644 index 00000000000..79b0466f248 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/buildview/events/BuildEventParser.kt @@ -0,0 +1,21 @@ +package com.google.idea.blaze.base.buildview.events + +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent +import com.google.idea.blaze.base.scope.output.IssueOutput +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.extensions.ExtensionPointName + +internal val LOG = Logger.getInstance(BuildEventParser::class.java) + +interface BuildEventParser { + companion object { + val EP_NAME: ExtensionPointName = + ExtensionPointName.create("com.google.idea.blaze.BuildEventParser") + + fun parse(event: BuildEvent): IssueOutput? { + return EP_NAME.extensions.firstNotNullOfOrNull { it.parse(event) } + } + } + + fun parse(event: BuildEvent): IssueOutput? +} diff --git a/base/src/com/google/idea/blaze/base/command/BlazeCommand.java b/base/src/com/google/idea/blaze/base/command/BlazeCommand.java index d2c0b68c9e8..d33d7c911bd 100644 --- a/base/src/com/google/idea/blaze/base/command/BlazeCommand.java +++ b/base/src/com/google/idea/blaze/base/command/BlazeCommand.java @@ -56,6 +56,10 @@ public BlazeCommandName getName() { return name; } + public String getBinaryPath() { + return binaryPath; + } + public ImmutableList toArgumentList() { return ImmutableList.builder() .addAll(blazeStartupFlags) @@ -115,6 +119,10 @@ public Builder(String binaryPath, BlazeCommandName name, Project project) { AspectRepositoryProvider.getOverrideFlag(project).ifPresent(this::addBlazeFlags); } + public BlazeCommandName getName() { + return name; + } + private ImmutableList getArguments() { ImmutableList.Builder arguments = ImmutableList.builder(); arguments.addAll(blazeCmdlineFlags.build()); 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 d689fafd8cd..143dd3f253f 100644 --- a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java @@ -25,7 +25,6 @@ import com.google.idea.blaze.base.bazel.BazelExitCodeException.ThrowOption; 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.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; @@ -88,17 +87,9 @@ public BlazeBuildOutputs run( context.setHasError(); } context.output(SummaryOutput.output(SummaryOutput.Prefix.TIMESTAMP, "Build command finished. Retrieving BEP outputs ...")); - if (buildResultHelper instanceof BuildResultHelperBep) { - File outputFile = ((BuildResultHelperBep) buildResultHelper).getOutputFile(); - context.output(SummaryOutput.output(SummaryOutput.Prefix.TIMESTAMP, String.format("BEP file '%s' (%d bytes)", outputFile.getAbsolutePath(), outputFile.length()))); - } try { - Interner stringInterner = - Optional.ofNullable(context.getScope(SharedStringPoolScope.class)) - .map(SharedStringPoolScope::getStringInterner) - .orElse(null); context.output(SummaryOutput.output(SummaryOutput.Prefix.TIMESTAMP, "Parsing BEP outputs...")); - ParsedBepOutput buildOutput = buildResultHelper.getBuildOutput(stringInterner); + ParsedBepOutput buildOutput = buildResultHelper.getBuildOutput(); context.output(SummaryOutput.output(SummaryOutput.Prefix.TIMESTAMP, "Handling parsed BEP outputs...")); BlazeBuildOutputs blazeBuildOutputs = BlazeBuildOutputs.fromParsedBepOutput( buildResult, buildOutput); @@ -135,13 +126,7 @@ public BlazeTestResults runTest( return BlazeTestResults.NO_RESULTS; } context.output(PrintOutput.log("Build command finished. Retrieving BEP outputs...")); - try { - return buildResultHelper.getTestResults(Optional.empty()); - } catch (GetArtifactsException e) { - context.output(PrintOutput.log("Failed to get build outputs: " + e.getMessage())); - context.setHasError(); - return BlazeTestResults.NO_RESULTS; - } + return buildResultHelper.getTestResults(); } @Override diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java index 80af6c4aaf4..b3b41306792 100644 --- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java +++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java @@ -28,6 +28,8 @@ public final class BuildEventProtocolUtils { // Instructs BEP to use local file paths (file://...) rather than objfs blobids. private static final String LOCAL_FILE_PATHS = "--nobuild_event_binary_file_path_conversion"; + // Instructs BEP to emit events for all actions. + private static final String PUBLISH_ALL_ACTIONS = "--build_event_publish_all_actions"; // A vm option overriding the directory used for the BEP output file. private static final String BEP_OUTPUT_FILE_VM_OVERRIDE = "bazel.bep.path"; @@ -55,7 +57,11 @@ public static File createTempOutputFile() { /** Returns a build flag instructing blaze to write build events to the given output file. */ public static ImmutableList getBuildFlags(File outputFile) { - return ImmutableList.of("--build_event_binary_file=" + outputFile.getPath(), LOCAL_FILE_PATHS); + return ImmutableList.of( + "--build_event_binary_file=" + outputFile.getPath(), + LOCAL_FILE_PATHS, + PUBLISH_ALL_ACTIONS + ); } /** diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventStreamProvider.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventStreamProvider.java index 8cc55a8633c..6229c3b1ebb 100644 --- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventStreamProvider.java +++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventStreamProvider.java @@ -55,17 +55,10 @@ static BuildEventStreamProvider fromInputStream(InputStream stream) { public BuildEvent getNext() throws BuildEventStreamException { return parseNextEventFromStream(countingStream); } - - @Override - public long getBytesConsumed() { - return countingStream.getCount(); - } }; } /** Returns the next build event in the stream, or null if there are none remaining. */ @Nullable BuildEventStreamProtos.BuildEvent getNext() throws BuildEventStreamException; - - long getBytesConsumed(); } diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java deleted file mode 100644 index 8e7a6cc95ee..00000000000 --- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2017 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.buildresult; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Interner; -import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.run.testlogs.BlazeTestResults; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.common.artifact.OutputArtifact; -import com.google.idea.blaze.exception.BuildException; -import java.io.InputStream; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** Assists in getting build artifacts from a build operation. */ -public interface BuildResultHelper extends AutoCloseable { - - /** - * Returns the build flags necessary for the build result helper to work. - * - *

The user must add these flags to their build command. - */ - List getBuildFlags(); - - /** - * Parses the BEP output data and returns the corresponding {@link ParsedBepOutput}. May only be - * called once, after the build is complete. - * - *

As BEP retrieval can be memory-intensive for large projects, implementations of - * getBuildOutput may restrict parallelism for cases in which many builds are executed in parallel - * (e.g. remote builds). - */ - default ParsedBepOutput getBuildOutput() throws GetArtifactsException { - return getBuildOutput(Optional.empty()); - } - - /** - * Parses the BEP output data and returns the corresponding {@link ParsedBepOutput}. May only be - * called once, after the build is complete. - * - *

As BEP retrieval can be memory-intensive for large projects, implementations of - * getBuildOutput may restrict parallelism for cases in which many builds are executed in parallel - * (e.g. remote builds). - */ - default ParsedBepOutput getBuildOutput(Interner stringInterner) - throws GetArtifactsException { - return getBuildOutput(Optional.empty(), stringInterner); - } - - /** - * Parses the BEP output data and returns the corresponding {@link ParsedBepOutput}. May only be - * called once, after the build is complete. - * - *

As BEP retrieval can be memory-intensive for large projects, implementations of - * getBuildOutput may restrict parallelism for cases in which many builds are executed in parallel - * (e.g. remote builds). - */ - default ParsedBepOutput getBuildOutput( - Optional completionBuildId, Interner stringInterner) - throws GetArtifactsException { - return getBuildOutput(completionBuildId); - } - - /** - * Retrieves BEP build events according to given id, parses them and returns the corresponding - * {@link ParsedBepOutput}. May only be called once, after the build is complete. - * - *

As BEP retrieval can be memory-intensive for large projects, implementations of - * getBuildOutput may restrict parallelism for cases in which many builds are executed in parallel - * (e.g. remote builds). - */ - ParsedBepOutput getBuildOutput(Optional completedBuildId) throws GetArtifactsException; - - /** - * Retrieves test results, parses them and returns the corresponding {@link BlazeTestResults}. May - * only be called once, after the build is complete. - */ - BlazeTestResults getTestResults(Optional completedBuildId) throws GetArtifactsException; - - /** Deletes the local BEP output file associated with the test results */ - default void deleteTemporaryOutputFiles() {} - - /** - * Parses the BEP output data to collect all build flags used. Return all flags that pass filters - */ - BuildFlags getBlazeFlags(Optional completedBuildId) throws GetFlagsException; - - /** - * Parses the BEP output data to collect message on stdout. - * - *

This function is designed for remote build which does not have local console output. Local - * build should not use this since {@link ExternalTask} provide stdout handler. - * - * @param completedBuildId build id. - * @param stderrConsumer process stderr - * @param blazeContext blaze context may contains logging scope - * @return a list of message on stdout. - */ - default InputStream getStdout( - String completedBuildId, Consumer stderrConsumer, BlazeContext blazeContext) - throws BuildException { - return InputStream.nullInputStream(); - } - - /** - * Parses the BEP output data to collect message on stderr. - * - *

This function is designed for remote build which does not have local console output. Local - * build should not use this since {@link ExternalTask} provide stderr handler. - * - * @param completedBuildId build id. - * @return a list of message on stderr. - */ - default InputStream getStderr(String completedBuildId) throws BuildException { - return InputStream.nullInputStream(); - } - - /** - * Parses the BEP output data to collect all build flags used. Return all flags that pass filters - */ - default BuildFlags getBlazeFlags() throws GetFlagsException { - return getBlazeFlags(Optional.empty()); - } - - /** - * Returns the build result. May only be called once, after the build is complete, or no artifacts - * will be returned. - * - * @return The build artifacts from the build operation. - */ - default ImmutableList getAllOutputArtifacts(Predicate pathFilter) - throws GetArtifactsException { - return getBuildOutput().getAllOutputArtifacts(pathFilter).asList(); - } - - /** - * Returns the build artifacts, filtering out all artifacts not directly produced by the specified - * target. - * - *

May only be called once, after the build is complete, or no artifacts will be returned. - */ - default ImmutableList getBuildArtifactsForTarget( - Label target, Predicate pathFilter) throws GetArtifactsException { - return getBuildOutput().getDirectArtifactsForTarget(target, pathFilter).asList(); - } - - /** - * Returns all build artifacts belonging to the given output groups. May only be called once, - * after the build is complete, or no artifacts will be returned. - */ - default ImmutableList getArtifactsForOutputGroup( - String outputGroup, Predicate pathFilter) throws GetArtifactsException { - return getBuildOutput().getOutputGroupArtifacts(outputGroup, pathFilter); - } - - @Override - void close(); - - /** Indicates a failure to get artifact information */ - class GetArtifactsException extends BuildException { - public GetArtifactsException(Throwable cause) { - super(cause); - } - - public GetArtifactsException(String message) { - super(message); - } - - public GetArtifactsException(String message, Throwable cause) { - super(message, cause); - } - } - - /** Indicates a failure to get artifact information */ - class GetFlagsException extends Exception { - public GetFlagsException(String message, Throwable cause) { - super(message, cause); - } - - public GetFlagsException(String message) { - super(message); - } - - public GetFlagsException(Throwable cause) { - super(cause); - } - } -} diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.kt b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.kt new file mode 100644 index 00000000000..00d7f33e81a --- /dev/null +++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2017 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.buildresult + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent +import com.google.idea.blaze.base.command.buildresult.BuildEventStreamProvider.BuildEventStreamException +import com.google.idea.blaze.base.model.primitives.Label +import com.google.idea.blaze.base.run.testlogs.BlazeTestResults +import com.google.idea.blaze.common.artifact.OutputArtifact +import com.google.idea.blaze.exception.BuildException +import com.intellij.openapi.diagnostic.Logger +import java.io.BufferedInputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.util.* +import java.util.function.Predicate + +private val LOG = Logger.getInstance(BuildResultHelper::class.java) + +/** + * Build event protocol implementation to get build results. + * + * The build even protocol (BEP for short) is a proto-based protocol used by bazel to communicate + * build events. + */ +class BuildResultHelper(val outputFile: File) : AutoCloseable { + + constructor() : this(BuildEventProtocolUtils.createTempOutputFile()) + + /** + * Returns the build flags necessary for the build result helper to work. + * + *

The user must add these flags to their build command. + */ + fun getBuildFlags(): List = BuildEventProtocolUtils.getBuildFlags(outputFile) + + /** + * Parses the BEP output data and returns the corresponding {@link ParsedBepOutput}. + */ + @Throws(GetArtifactsException::class) + fun getBuildOutput(): ParsedBepOutput { + return try { + BufferedInputStream(FileInputStream(outputFile)).use(ParsedBepOutput::parseBepArtifacts) + } catch (e: IOException) { + LOG.error(e) + throw GetArtifactsException(e.message) + } catch (e: BuildEventStreamException) { + LOG.error(e) + throw GetArtifactsException(e.message) + } + } + + /** + * Parses the BEP output data and returns the corresponding {@link ParsedBepOutput}. + */ + fun getTestResults(): BlazeTestResults { + return try { + BufferedInputStream(FileInputStream(outputFile)).use(BuildEventProtocolOutputReader::parseTestResults) + } catch (e: IOException) { + LOG.warn(e) + return BlazeTestResults.NO_RESULTS + } catch (e: BuildEventStreamException) { + LOG.warn(e) + return BlazeTestResults.NO_RESULTS + } + } + + fun deleteTemporaryOutputFiles() { + outputFile.delete() + } + + @Throws(GetFlagsException::class) + fun getBlazeFlags(): BuildFlags { + return try { + BufferedInputStream(FileInputStream(outputFile)).use(BuildFlags::parseBep) + } catch (e: IOException) { + throw GetFlagsException(e) + } catch (e: BuildEventStreamException) { + throw GetFlagsException(e) + } + } + + /** + * Returns the build artifacts, filtering out all artifacts not directly produced by the specified + * target. + */ + @Throws(GetArtifactsException::class) + fun getBuildArtifactsForTarget( + target: Label, + pathFilter: Predicate, + ): ImmutableSet { + return getBuildOutput().getDirectArtifactsForTarget(target, pathFilter) + } + + /** + * Returns all build artifacts belonging to the given output groups. + */ + @Throws(GetArtifactsException::class) + fun getArtifactsForOutputGroup( + outputGroup: String, + pathFilter: Predicate, + ): ImmutableList { + return getBuildOutput().getOutputGroupArtifacts(outputGroup, pathFilter) + } + + override fun close() { + deleteTemporaryOutputFiles() + } + + class GetArtifactsException : BuildException { + constructor(cause: Throwable?) : super(cause) + + constructor(message: String?) : super(message) + + constructor(message: String?, cause: Throwable?) : super(message, cause) + } + + class GetFlagsException(cause: Throwable?) : Exception(cause) +} diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java deleted file mode 100644 index bfb24bda2f9..00000000000 --- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2017 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.buildresult; - -import com.google.idea.blaze.base.command.buildresult.BuildEventStreamProvider.BuildEventStreamException; -import com.google.idea.blaze.base.io.InputStreamProvider; -import com.google.idea.blaze.base.run.testlogs.BlazeTestResults; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Optional; -import org.jetbrains.annotations.VisibleForTesting; - -/** - * Build event protocol implementation to get build results. - * - *

The build even protocol (BEP for short) is a proto-based protocol used by bazel to communicate - * build events. - */ -public class BuildResultHelperBep implements BuildResultHelper { - - private static final Logger logger = Logger.getInstance(BuildResultHelperBep.class); - private final File outputFile; - - public BuildResultHelperBep() { - outputFile = BuildEventProtocolUtils.createTempOutputFile(); - } - - @VisibleForTesting - public BuildResultHelperBep(File outputFile) { - this.outputFile = outputFile; - } - - @Override - public List getBuildFlags() { - return BuildEventProtocolUtils.getBuildFlags(outputFile); - } - - @Override - public ParsedBepOutput getBuildOutput(Optional completedBuildId) - throws GetArtifactsException { - try (InputStream inputStream = new BufferedInputStream(new FileInputStream(outputFile))) { - return ParsedBepOutput.parseBepArtifacts(inputStream); - } catch (IOException | BuildEventStreamException e) { - logger.error(e); - throw new GetArtifactsException(e.getMessage()); - } - } - - @Override - public BlazeTestResults getTestResults(Optional completedBuildId) { - try (InputStream inputStream = - new BufferedInputStream(InputStreamProvider.getInstance().forFile(outputFile))) { - return BuildEventProtocolOutputReader.parseTestResults(inputStream); - } catch (IOException | BuildEventStreamException e) { - logger.warn(e); - return BlazeTestResults.NO_RESULTS; - } - } - - @Override - public void deleteTemporaryOutputFiles() { - if (!outputFile.delete()) { - logger.warn("Could not delete BEP output file: " + outputFile); - } - } - - @Override - public BuildFlags getBlazeFlags(Optional completedBuildId) throws GetFlagsException { - try (InputStream inputStream = new BufferedInputStream(new FileInputStream(outputFile))) { - return BuildFlags.parseBep(inputStream); - } catch (IOException | BuildEventStreamException e) { - throw new GetFlagsException(e); - } - } - - @Override - public void close() { - if (!outputFile.delete()) { - logger.warn("Could not delete BEP output file: " + outputFile); - } - } - - public File getOutputFile() { - return outputFile; - } - - static class Provider implements BuildResultHelperProvider { - - @Override - public Optional doCreateForLocalBuild(Project project) { - return Optional.of(new BuildResultHelperBep()); - } - } -} diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperProvider.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperProvider.java deleted file mode 100644 index f8798190f6c..00000000000 --- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 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.buildresult; - -import com.google.errorprone.annotations.MustBeClosed; -import com.google.idea.blaze.base.bazel.BuildSystem.BuildInvoker; -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.project.Project; -import java.util.Optional; - -/** - * Determines which {@link BuildResultHelper} to use for the current project. - * - * @deprecated Use {@link BuildInvoker#createBuildResultProvider()} instead which will create a - * result helper appropriate for that build invoker. - */ -@Deprecated -public interface BuildResultHelperProvider { - - ExtensionPointName EP_NAME = - ExtensionPointName.create("com.google.idea.blaze.BuildResultHelperProvider"); - - /** - * Constructs a BuildResultHelper that supports a local BEP and artifacts. This is required - * because parts of Blaze Plugin implicitly depended on a {@link BuildResultHelper} corresponding - * to local builds. - * - * @deprecated All consumers should be migrated to use {@link - * com.google.idea.blaze.base.bazel.BuildSystem#getBuildInvoker(Project)} and {handle local or - * {@link BuildInvoker#createBuildResultProvider()}. - */ - @Deprecated - Optional doCreateForLocalBuild(Project project); - - /** - * Constructs a new build result helper for local builds. - * - * @deprecated Use {@link BuildInvoker#createBuildResultProvider()} instead which will create a - * result helper appropriate for that build invoker. - */ - @Deprecated - @MustBeClosed - static BuildResultHelper createForLocalBuild(Project project) { - for (BuildResultHelperProvider extension : EP_NAME.getExtensions()) { - Optional helper = extension.doCreateForLocalBuild(project); - if (helper.isPresent()) { - return helper.get(); - } - } - return new BuildResultHelperBep(); - } -} diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/ParsedBepOutput.java b/base/src/com/google/idea/blaze/base/command/buildresult/ParsedBepOutput.java index 8919bfcf609..7027fb608d7 100644 --- a/base/src/com/google/idea/blaze/base/command/buildresult/ParsedBepOutput.java +++ b/base/src/com/google/idea/blaze/base/command/buildresult/ParsedBepOutput.java @@ -71,7 +71,6 @@ public final class ParsedBepOutput { ImmutableSetMultimap.of(), 0, BuildResult.SUCCESS, - 0, ImmutableSet.of()); private static final String WORKSPACE_ITEM_KEY_SOURCE_URI = "SOURCE_URI"; @@ -200,7 +199,6 @@ public static ParsedBepOutput parseBepArtifacts( targetToFileSets.build(), startTimeMillis, buildResult, - stream.getBytesConsumed(), targetsWithErrors.build()); } @@ -260,7 +258,6 @@ private static ImmutableMap fillInTransitiveFileSetData( final long syncStartTimeMillis; private final BuildResult buildResult; - private final long bepBytesConsumed; private final ImmutableSet