From 9301bc25891d929fa944c40bbc4189f834f6fd90 Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Tue, 18 Apr 2023 15:13:09 +0300 Subject: [PATCH 1/2] Make externalHooks public This key has been added in 4061dabf4d3a999099dc9e93238a9d81e6bc7cfe but it is only available to Sbt itself. Since ExternalHooks is a Java interface, defined in Zinc for a while and fairly stable, I think this should be safe to do. My main motivation is to allow installing an InvalidationProfiler from an Sbt plugin, thus gaining access to zinc recompilation decisions. See related PR https://github.com/sbt/zinc/pull/1181 --- main/src/main/scala/sbt/Keys.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index cd5b520c0..42fbafceb 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -248,7 +248,7 @@ object Keys { val copyResources = taskKey[Seq[(File, File)]]("Copies resources to the output directory.").withRank(AMinusTask) val aggregate = settingKey[Boolean]("Configures task aggregation.").withRank(BMinusSetting) val sourcePositionMappers = taskKey[Seq[xsbti.Position => Option[xsbti.Position]]]("Maps positions in generated source files to the original source it was generated from").withRank(DTask) - private[sbt] val externalHooks = taskKey[ExternalHooks]("The external hooks used by zinc.") + val externalHooks = taskKey[ExternalHooks]("The external hooks used by zinc.") val auxiliaryClassFiles = taskKey[Seq[AuxiliaryClassFiles]]("The auxiliary class files that must be managed by Zinc (for instance the TASTy files)") val fileConverter = settingKey[FileConverter]("The file converter used to convert between Path and VirtualFile") val allowMachinePath = settingKey[Boolean]("Allow machine-specific paths during conversion.") @@ -426,7 +426,7 @@ object Keys { val bspBuildTargetJVMRunEnvironment = inputKey[Unit]("Corresponds to the buildTarget/jvmRunEnvironment request").withRank(DTask) val bspBuildTargetJVMTestEnvironment = inputKey[Unit]("Corresponds to the buildTarget/jvmTestEnvironment request").withRank(DTask) val bspBuildTargetJvmEnvironmentItem = taskKey[JvmEnvironmentItem]("Computes JVM environment item").withRank(DTask) - + val bspScalaTestClasses = inputKey[Unit]("Corresponds to buildTarget/scalaTestClasses request").withRank(DTask) val bspScalaTestClassesItem = taskKey[Seq[ScalaTestClassesItem]]("").withRank(DTask) val bspScalaMainClasses = inputKey[Unit]("Corresponds to buildTarget/scalaMainClasses request").withRank(DTask) From 6dfebc689b074b0da81c51543914048ffda06e71 Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Mon, 24 Apr 2023 12:33:09 +0200 Subject: [PATCH 2/2] Add a key for Zinc listeners. Expose what the incremental compiler is doing behind the scenes. The RunProfiler interface has been part of Zinc for a while, but this allows the build itself, or an Sbt plugin, to hook their own implementation. We expose a list of such listeners to avoid plugins stepping on each other and replacing an existing listener. --- main/src/main/scala/sbt/Defaults.scala | 7 ++- main/src/main/scala/sbt/Keys.scala | 3 +- .../sbt/internal/DefaultRunProfiler.scala | 53 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 main/src/main/scala/sbt/internal/DefaultRunProfiler.scala diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 92add3fb5..613bdf39e 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -946,11 +946,16 @@ object Defaults extends BuildCommon { compileAnalysisTargetRoot.value / compileAnalysisFilename.value }, externalHooks := IncOptions.defaultExternal, + zincCompilationListeners := Seq.empty, incOptions := { val old = incOptions.value + val extHooks = externalHooks.value + val newExtHooks = extHooks.withInvalidationProfiler( + () => new DefaultRunProfiler(zincCompilationListeners.value) + ) old .withAuxiliaryClassFiles(auxiliaryClassFiles.value.toArray) - .withExternalHooks(externalHooks.value) + .withExternalHooks(newExtHooks) .withClassfileManagerType( Option( TransactionalManagerType diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 42fbafceb..92e0c2b02 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -248,7 +248,8 @@ object Keys { val copyResources = taskKey[Seq[(File, File)]]("Copies resources to the output directory.").withRank(AMinusTask) val aggregate = settingKey[Boolean]("Configures task aggregation.").withRank(BMinusSetting) val sourcePositionMappers = taskKey[Seq[xsbti.Position => Option[xsbti.Position]]]("Maps positions in generated source files to the original source it was generated from").withRank(DTask) - val externalHooks = taskKey[ExternalHooks]("The external hooks used by zinc.") + private[sbt] val externalHooks = taskKey[ExternalHooks]("The external hooks used by zinc.") + val zincCompilationListeners = settingKey[Seq[RunProfiler]]("Listeners that receive information about incremental compiler decisions.").withRank(DSetting) val auxiliaryClassFiles = taskKey[Seq[AuxiliaryClassFiles]]("The auxiliary class files that must be managed by Zinc (for instance the TASTy files)") val fileConverter = settingKey[FileConverter]("The file converter used to convert between Path and VirtualFile") val allowMachinePath = settingKey[Boolean]("Allow machine-specific paths during conversion.") diff --git a/main/src/main/scala/sbt/internal/DefaultRunProfiler.scala b/main/src/main/scala/sbt/internal/DefaultRunProfiler.scala new file mode 100644 index 000000000..8878729af --- /dev/null +++ b/main/src/main/scala/sbt/internal/DefaultRunProfiler.scala @@ -0,0 +1,53 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import xsbti.VirtualFileRef +import xsbti.compile.{ APIChange, InitialChanges, RunProfiler } + +class DefaultRunProfiler(profilers: Seq[RunProfiler]) extends RunProfiler { + override def timeCompilation(startNanos: Long, durationNanos: Long): Unit = + profilers.foreach(_.timeCompilation(startNanos, durationNanos)) + + override def registerInitial(changes: InitialChanges): Unit = + profilers.foreach(_.registerInitial(changes)) + + override def registerEvent( + kind: String, + inputs: Array[String], + outputs: Array[String], + reason: String + ): Unit = + profilers.foreach(_.registerEvent(kind, inputs, outputs, reason)) + + override def registerCycle( + invalidatedClasses: Array[String], + invalidatedPackageObjects: Array[String], + initialSources: Array[VirtualFileRef], + invalidatedSources: Array[VirtualFileRef], + recompiledClasses: Array[String], + changesAfterRecompilation: Array[APIChange], + nextInvalidations: Array[String], + shouldCompileIncrementally: Boolean + ): Unit = + profilers.foreach( + _.registerCycle( + invalidatedClasses, + invalidatedPackageObjects, + initialSources, + invalidatedSources, + recompiledClasses, + changesAfterRecompilation, + nextInvalidations, + shouldCompileIncrementally + ) + ) + + override def registerRun(): Unit = + profilers.foreach(_.registerRun()) +}