From 02df82840a1527fb31d32c33d5bdb9d2bec034c4 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Thu, 15 Feb 2024 15:36:05 +0100 Subject: [PATCH] add noOp field in BSP compile report --- main/src/main/scala/sbt/Defaults.scala | 6 ++-- .../sbt/internal/server/BspCompileTask.scala | 34 +++++++++++++------ .../sbt/internal/bsp/CompileReport.scala | 24 ++++++++----- .../bsp/codec/CompileReportFormats.scala | 4 ++- protocol/src/main/contraband/bsp.contra | 3 ++ .../test/scala/testpkg/BuildServerTest.scala | 15 ++++---- 6 files changed, 55 insertions(+), 31 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 5364c68ab..f3cd01835 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2316,11 +2316,11 @@ object Defaults extends BuildCommon { val ci = (compile / compileInputs).value val ping = earlyOutputPing.value val reporter = (compile / bspReporter).value - BspCompileTask.compute(bspTargetIdentifier.value, thisProjectRef.value, configuration.value) { - task => + BspCompileTask + .compute(bspTargetIdentifier.value, thisProjectRef.value, configuration.value, ci) { task => // TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too? compileIncrementalTaskImpl(task, s, ci, ping, reporter) - } + } } private val incCompiler = ZincUtil.defaultIncrementalCompiler private[sbt] def compileJavaTask: Initialize[Task[CompileResult]] = Def.task { diff --git a/main/src/main/scala/sbt/internal/server/BspCompileTask.scala b/main/src/main/scala/sbt/internal/server/BspCompileTask.scala index 99cd0898a..205b11b57 100644 --- a/main/src/main/scala/sbt/internal/server/BspCompileTask.scala +++ b/main/src/main/scala/sbt/internal/server/BspCompileTask.scala @@ -11,21 +11,31 @@ package sbt.internal.server import sbt._ import sbt.internal.bsp._ import sbt.internal.io.Retry -import sbt.internal.server.BspCompileTask.{ compileReport, exchange } +import sbt.internal.server.BspCompileTask.compileReport +import sbt.internal.server.BspCompileTask.exchange import sbt.librarymanagement.Configuration +import sbt.util.InterfaceUtil import sjsonnew.support.scalajson.unsafe.Converter +import xsbti.CompileFailed +import xsbti.Problem +import xsbti.Severity import xsbti.compile.CompileResult -import xsbti.{ CompileFailed, Problem, Severity } +import xsbti.compile.Inputs import scala.util.control.NonFatal object BspCompileTask { private lazy val exchange = StandardMain.exchange - def compute(targetId: BuildTargetIdentifier, project: ProjectRef, config: Configuration)( + def compute( + targetId: BuildTargetIdentifier, + project: ProjectRef, + config: Configuration, + ci: Inputs + )( compile: BspCompileTask => CompileResult ): CompileResult = { - val task = BspCompileTask(targetId, project, config) + val task = BspCompileTask(targetId, project, config, ci) try { task.notifyStart() val result = Retry(compile(task)) @@ -45,22 +55,24 @@ object BspCompileTask { private def apply( targetId: BuildTargetIdentifier, project: ProjectRef, - config: Configuration + config: Configuration, + inputs: Inputs ): BspCompileTask = { val taskId = TaskId(BuildServerTasks.uniqueId, Vector()) val targetName = BuildTargetName.fromScope(project.project, config.name) - BspCompileTask(targetId, targetName, taskId, System.currentTimeMillis()) + new BspCompileTask(targetId, targetName, taskId, inputs, System.currentTimeMillis()) } private def compileReport( problems: Seq[Problem], targetId: BuildTargetIdentifier, - elapsedTimeMillis: Long + elapsedTimeMillis: Long, + isNoOp: Option[Boolean] ): CompileReport = { val countBySeverity = problems.groupBy(_.severity()).mapValues(_.size) val warnings = countBySeverity.getOrElse(Severity.Warn, 0) val errors = countBySeverity.getOrElse(Severity.Error, 0) - CompileReport(targetId, None, errors, warnings, Some(elapsedTimeMillis.toInt)) + CompileReport(targetId, None, errors, warnings, Some(elapsedTimeMillis.toInt), isNoOp) } } @@ -68,6 +80,7 @@ case class BspCompileTask private ( targetId: BuildTargetIdentifier, targetName: String, id: TaskId, + inputs: Inputs, startTimeMillis: Long ) { import sbt.internal.bsp.codec.JsonProtocol._ @@ -89,7 +102,8 @@ case class BspCompileTask private ( sourceInfos.values.flatMap(_.getReportedProblems).toSeq case _ => Seq() } - val report = compileReport(problems, targetId, elapsedTimeMillis) + val isNoOp = InterfaceUtil.toOption(inputs.previousResult.analysis).map(_ == result.analysis) + val report = compileReport(problems, targetId, elapsedTimeMillis, isNoOp) val params = TaskFinishParams( id, endTimeMillis, @@ -122,7 +136,7 @@ case class BspCompileTask private ( val endTimeMillis = System.currentTimeMillis() val elapsedTimeMillis = endTimeMillis - startTimeMillis val problems = cause.map(_.problems().toSeq).getOrElse(Seq.empty[Problem]) - val report = compileReport(problems, targetId, elapsedTimeMillis) + val report = compileReport(problems, targetId, elapsedTimeMillis, None) val params = TaskFinishParams( id, endTimeMillis, diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileReport.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileReport.scala index 5eb2ac41a..1144cd8a3 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileReport.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileReport.scala @@ -10,28 +10,30 @@ package sbt.internal.bsp * @param errors The total number of reported errors compiling this target. * @param warnings The total number of reported warnings compiling the target. * @param time The total number of milliseconds it took to compile the target. + * @param noOp The compilation was a noOp compilation. */ final class CompileReport private ( val target: sbt.internal.bsp.BuildTargetIdentifier, val originId: Option[String], val errors: Int, val warnings: Int, - val time: Option[Int]) extends Serializable { + val time: Option[Int], + val noOp: Option[Boolean]) extends Serializable { override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { - case x: CompileReport => (this.target == x.target) && (this.originId == x.originId) && (this.errors == x.errors) && (this.warnings == x.warnings) && (this.time == x.time) + case x: CompileReport => (this.target == x.target) && (this.originId == x.originId) && (this.errors == x.errors) && (this.warnings == x.warnings) && (this.time == x.time) && (this.noOp == x.noOp) case _ => false }) override def hashCode: Int = { - 37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.CompileReport".##) + target.##) + originId.##) + errors.##) + warnings.##) + time.##) + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.CompileReport".##) + target.##) + originId.##) + errors.##) + warnings.##) + time.##) + noOp.##) } override def toString: String = { - "CompileReport(" + target + ", " + originId + ", " + errors + ", " + warnings + ", " + time + ")" + "CompileReport(" + target + ", " + originId + ", " + errors + ", " + warnings + ", " + time + ", " + noOp + ")" } - private[this] def copy(target: sbt.internal.bsp.BuildTargetIdentifier = target, originId: Option[String] = originId, errors: Int = errors, warnings: Int = warnings, time: Option[Int] = time): CompileReport = { - new CompileReport(target, originId, errors, warnings, time) + private[this] def copy(target: sbt.internal.bsp.BuildTargetIdentifier = target, originId: Option[String] = originId, errors: Int = errors, warnings: Int = warnings, time: Option[Int] = time, noOp: Option[Boolean] = noOp): CompileReport = { + new CompileReport(target, originId, errors, warnings, time, noOp) } def withTarget(target: sbt.internal.bsp.BuildTargetIdentifier): CompileReport = { copy(target = target) @@ -54,9 +56,15 @@ final class CompileReport private ( def withTime(time: Int): CompileReport = { copy(time = Option(time)) } + def withNoOp(noOp: Option[Boolean]): CompileReport = { + copy(noOp = noOp) + } + def withNoOp(noOp: Boolean): CompileReport = { + copy(noOp = Option(noOp)) + } } object CompileReport { - def apply(target: sbt.internal.bsp.BuildTargetIdentifier, originId: Option[String], errors: Int, warnings: Int, time: Option[Int]): CompileReport = new CompileReport(target, originId, errors, warnings, time) - def apply(target: sbt.internal.bsp.BuildTargetIdentifier, originId: String, errors: Int, warnings: Int, time: Int): CompileReport = new CompileReport(target, Option(originId), errors, warnings, Option(time)) + def apply(target: sbt.internal.bsp.BuildTargetIdentifier, originId: Option[String], errors: Int, warnings: Int, time: Option[Int], noOp: Option[Boolean]): CompileReport = new CompileReport(target, originId, errors, warnings, time, noOp) + def apply(target: sbt.internal.bsp.BuildTargetIdentifier, originId: String, errors: Int, warnings: Int, time: Int, noOp: Boolean): CompileReport = new CompileReport(target, Option(originId), errors, warnings, Option(time), Option(noOp)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileReportFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileReportFormats.scala index 953258a86..dde3a3598 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileReportFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileReportFormats.scala @@ -16,8 +16,9 @@ implicit lazy val CompileReportFormat: JsonFormat[sbt.internal.bsp.CompileReport val errors = unbuilder.readField[Int]("errors") val warnings = unbuilder.readField[Int]("warnings") val time = unbuilder.readField[Option[Int]]("time") + val noOp = unbuilder.readField[Option[Boolean]]("noOp") unbuilder.endObject() - sbt.internal.bsp.CompileReport(target, originId, errors, warnings, time) + sbt.internal.bsp.CompileReport(target, originId, errors, warnings, time, noOp) case None => deserializationError("Expected JsObject but found None") } @@ -29,6 +30,7 @@ implicit lazy val CompileReportFormat: JsonFormat[sbt.internal.bsp.CompileReport builder.addField("errors", obj.errors) builder.addField("warnings", obj.warnings) builder.addField("time", obj.time) + builder.addField("noOp", obj.noOp) builder.endObject() } } diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index dd621786a..a931a8fb6 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -530,6 +530,9 @@ type CompileReport { ## The total number of milliseconds it took to compile the target. time: Int + + ## The compilation was a noOp compilation. + noOp: Boolean } ## Test Request diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index b5ba98cb2..0e79673b7 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -133,7 +133,6 @@ object BuildServerTest extends AbstractServerTest { |}""".stripMargin ) - reloadWorkspace() compile(buildTarget) assertMessage( @@ -156,22 +155,20 @@ object BuildServerTest extends AbstractServerTest { reloadWorkspace() compile(buildTarget) - assertMessage( - "build/publishDiagnostics", - "Diagnostics.scala", - "\"diagnostics\":[]" - )( + assertMessage("build/publishDiagnostics", "Diagnostics.scala", "\"diagnostics\":[]")( duration = 30.seconds, message = "should send publishDiagnostics with empty diagnostics" ) + assertMessage("build/taskFinish", "\"noOp\":true")(debug = true) // trigger no-op compilation compile(buildTarget) assert( - !svr.waitForString(20.seconds) { s => - s.contains("build/publishDiagnostics") && - s.contains("Diagnostics.scala") + svr.waitForString(20.seconds) { s => + if (s.contains("build/publishDiagnostics") && s.contains("Diagnostics.scala")) + throw new Exception("shouldn't send publishDiagnostics if noOp compilation") + else s.contains("build/taskFinish") && s.contains("\"noOp\":true") }, "shouldn't send publishDiagnostics if there's no change in diagnostics (were empty, are empty)" )