From a53f088f1f676267c6bf56b1151f807f378ae74c Mon Sep 17 00:00:00 2001 From: Roberto Tyley Date: Wed, 20 Sep 2023 21:39:53 +0100 Subject: [PATCH] Support establishing the actual level of compatability --- .../sbtversionpolicy/Compatibility.scala | 33 ++++++++-- .../SbtVersionPolicyKeys.scala | 5 +- .../SbtVersionPolicySettings.scala | 62 +++++++++---------- 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala index 7e4c118..25f5472 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala @@ -1,25 +1,46 @@ package sbtversionpolicy -import coursier.version.{ Version, VersionCompatibility } -import sbt.VersionNumber +import com.typesafe.tools.mima.plugin.MimaPlugin +import coursier.version.{Version, VersionCompatibility} +import sbt.{TaskKey, VersionNumber} +import sbtversionpolicy.SbtVersionPolicyPlugin.autoImport /** Compatibility level between two version values. */ -sealed trait Compatibility +sealed trait Compatibility { + val checkThatMustPassForCompatabilityLevel: Option[TaskKey[_]] + val shortDescription: String +} object Compatibility { /** There is NO source compatibility or binary compatibility. */ - case object None extends Compatibility + case object None extends Compatibility { + override val checkThatMustPassForCompatabilityLevel: Option[TaskKey[_]] = scala.None + override val shortDescription: String = "not" + } /** Binary compatibility only. */ - case object BinaryCompatible extends Compatibility + case object BinaryCompatible extends Compatibility { + override val checkThatMustPassForCompatabilityLevel: Option[TaskKey[_]] = + Some(MimaPlugin.autoImport.mimaReportBinaryIssues) + override val shortDescription: String = "binary" + } /** Binary and source compatibility. */ - case object BinaryAndSourceCompatible extends Compatibility + case object BinaryAndSourceCompatible extends Compatibility { + override val checkThatMustPassForCompatabilityLevel: Option[TaskKey[_]] = + Some(autoImport.versionPolicyForwardCompatibilityCheck) + override val shortDescription: String = "binary and source" + } + + // Ordered from least to MOST exacting + val Levels: Seq[Compatibility] = Seq(None, BinaryCompatible, BinaryAndSourceCompatible) + + implicit val compatibilityOrdering: Ordering[Compatibility] = Ordering.by[Compatibility, Int](Levels.indexOf(_)) def apply(value1: String, value2: String, scheme: VersionCompatibility): Compatibility = { def get(idx: Int, items: Vector[Version.Item]) = diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala index 797cb17..500bccc 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala @@ -7,7 +7,10 @@ import sbt.librarymanagement.DependencyBuilders.OrganizationArtifactName import scala.util.matching.Regex trait SbtVersionPolicyKeys { - final val versionPolicyIntention = settingKey[Compatibility]("Compatibility intentions for the next release.") + final val versionPolicyIntention = settingKey[Compatibility]("Compatibility intentions for the next release.") + final val versionAssessMimaCompatibility = taskKey[Compatibility]("The compatability level of the code, based on the current state of the project.") + final val versionAssessDependencyCompatibility = taskKey[Compatibility]("The compatability level of the dependencies.") + final val versionAssessOverallCompatibility = taskKey[Compatibility]("The overall compatability level of the code & dependencies.") final val versionPolicyPreviousArtifacts = taskKey[Seq[ModuleID]]("") final val versionPolicyReportDependencyIssues = taskKey[Unit]("Check for removed or updated dependencies in an incompatible way.") final val versionPolicyCheck = taskKey[Unit]("Runs both versionPolicyReportDependencyIssues and versionPolicyMimaCheck") diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala index 1a01b15..7c9b4f2 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala @@ -2,13 +2,14 @@ package sbtversionpolicy import com.typesafe.tools.mima.plugin.{MimaPlugin, SbtMima} import coursier.version.{ModuleMatchers, Version, VersionCompatibility} -import sbt._ -import sbt.Keys._ +import sbt.{Def, *} +import sbt.Keys.* import sbt.librarymanagement.CrossVersion import lmcoursier.CoursierDependencyResolution import sbtversionpolicy.internal.{DependencyCheck, DependencySchemes, MimaIssues} -import sbtversionpolicy.SbtVersionPolicyMima.autoImport._ +import sbtversionpolicy.SbtVersionPolicyMima.autoImport.* +import scala.math.Ordering.Implicits._ import scala.util.Try object SbtVersionPolicySettings { @@ -272,47 +273,44 @@ object SbtVersionPolicySettings { } else Compatibility.None }, + versionAssessMimaCompatibility := { + lastPopulatedValueOf(Compatibility.Levels.toList.flatMap { level => + level.checkThatMustPassForCompatabilityLevel.map(_.result.map(_.toEither.toOption.map(_ => level))) + }).map(_.getOrElse(Compatibility.None)).value + }, versionPolicyMimaCheck := Def.taskDyn { - import Compatibility._ - val compatibility = + val intendedCompatibility = versionPolicyIntention.?.value .getOrElse(throw new MessageOnlyException("Please set the key versionPolicyIntention to declare the compatibility you want to check")) val log = streams.value.log - val currentModule = projectID.value + val currentModule = nameAndRevision(projectID.value) val formattedPreviousVersions = formatVersions(versionPolicyPreviousVersions.value) + val actualCompat = versionAssessMimaCompatibility.value - val reportBackwardBinaryCompatibilityIssues: Def.Initialize[Task[Unit]] = - MimaPlugin.autoImport.mimaReportBinaryIssues.result.map(_.toEither.left.foreach { error => - log.error(s"Module ${nameAndRevision(currentModule)} is not binary compatible with ${formattedPreviousVersions}. You have to relax your compatibility intention by changing the value of versionPolicyIntention.") - throw new MessageOnlyException(error.directCause.map(_.toString).getOrElse("mimaReportBinaryIssues failed")) - }) + println(s"actualCompat=$actualCompat") - val reportForwardBinaryCompatibilityIssues: Def.Initialize[Task[Unit]] = - versionPolicyForwardCompatibilityCheck.result.map(_.toEither.left.foreach { error => - log.error(s"Module ${nameAndRevision(currentModule)} is not source compatible with ${formattedPreviousVersions}. You have to relax your compatibility intention by changing the value of versionPolicyIntention.") - throw new MessageOnlyException(error.directCause.map(_.toString).getOrElse("versionPolicyForwardCompatibilityCheck failed")) + Def.task { + log.info(if (intendedCompatibility == Compatibility.None) { + s"Not checking compatibility of module $currentModule because versionPolicyIntention is set to 'Compatibility.None'" + } else { + s"Module $currentModule is ${actualCompat.shortDescription} compatible with $formattedPreviousVersions" }) - - compatibility match { - case BinaryCompatible => - reportBackwardBinaryCompatibilityIssues.map { _ => - log.info(s"Module ${nameAndRevision(currentModule)} is binary compatible with ${formattedPreviousVersions}") - } - case BinaryAndSourceCompatible => - Def.task { - val ignored1 = reportForwardBinaryCompatibilityIssues.value - val ignored2 = reportBackwardBinaryCompatibilityIssues.value - }.map { _ => - log.info(s"Module ${nameAndRevision(currentModule)} is binary and source compatible with ${formattedPreviousVersions}") - } - case None => Def.task { - // skip mima if no compatibility is intented - log.info(s"Not checking compatibility of module ${nameAndRevision(currentModule)} because versionPolicyIntention is set to 'Compatibility.None'") - } } }.value ) + private def lastPopulatedValueOf[B](tasks: List[Def.Initialize[Task[Option[B]]]]): Def.Initialize[Task[Option[B]]] = { + tasks match { + case Nil => Def.task(None) + case x :: xs => + Def.task { + val tv = x.value + if (tv.isDefined) lastPopulatedValueOf(xs).value.orElse(tv) + else None + } + } + } + def skipSettings = Seq( versionCheck / skip := (publish / skip).value, versionPolicyCheck / skip := (publish / skip).value