Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support establishing the actual level of compatibility #179

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package sbtversionpolicy

import scala.math.Ordered._

case class CompatAssessment(
attainedCompat: Compatibility,
failure: Option[Exception]
) {
for {
f <- failure
failedCompat <- Compatibility.Levels.find(_ > attainedCompat)
} yield (failedCompat, f)
}

object CompatAssessment {
def apply(): CompatAssessment = {

}
}
Original file line number Diff line number Diff line change
@@ -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]) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -272,47 +273,60 @@ object SbtVersionPolicySettings {
}
else Compatibility.None
},
versionAssessMimaCompatibility := {
/*
When being used for an Intention check, we want:
- the compatibility level attained
- the exception we got trying to attain that compatability level, if any (which would be from the level *above* the attained level...)

We want to say "project failed the binary compatability check with exception waawaah"

log.error(s"Module ${nameAndRevision(currentModule)} is not {intended} 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"))

result.attainsCompatability = BinaryCompatible
result.failedToAttain = Some((SourceAndBinaryCompatible, exception))

*/
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 actualCompat2 = versionAssessMimaCompatibility.value
val actualCompat3 = 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
Expand Down
Loading