Skip to content

Commit

Permalink
Fix implementation of versionPolicyFindDependencyIssues
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrf committed Nov 29, 2023
1 parent ac7a294 commit 75051c4
Show file tree
Hide file tree
Showing 20 changed files with 143 additions and 207 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,10 @@ In this mode, you can use sbt-version-policy to assess the incompatibilities int
2. define `releaseVersion` from the compatibility level returned by `versionPolicyAssessCompatibility`
~~~ scala
releaseVersion := {
val maybeBump = versionPolicyAssessCompatibility.value match {
val compatibilityWithPreviousReleases = versionPolicyAssessCompatibility.value
val compatibilityWithLastRelease = compatibilityWithPreviousReleases.head
val (_, compatibility) = compatibilityWithLastRelease
val maybeBump = compatibility match {
case Compatibility.None => Some(Version.Bump.Major)
case Compatibility.BinaryCompatible => Some(Version.Bump.Minor)
case Compatibility.BinaryAndSourceCompatible => None // No need to bump the patch version, because it has already been bumped when sbt-release set the next release version
Expand Down
9 changes: 2 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ inThisBuild(List(
url("https://github.com/alexarchambault")
)
),
versionPolicyIntention := Compatibility.BinaryAndSourceCompatible,
versionPolicyIntention := Compatibility.None,
libraryDependencySchemes += "com.typesafe" %% "mima-core" % "semver-spec"
))

Expand All @@ -29,18 +29,13 @@ lazy val `sbt-version-policy` = project
scriptedLaunchOpts += "-Dplugin.version=" + version.value,
scriptedBufferLog := false,
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.3"),
libraryDependencies ++= Seq(
"io.github.alexarchambault" %% "data-class" % "0.2.6" % Provided,
compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
),
libraryDependencies ++= Seq(
"io.get-coursier" % "interface" % "1.0.18",
"io.get-coursier" %% "versions" % "0.3.1",
"com.eed3si9n.verify" %% "verify" % "2.0.1" % Test,
),
testFrameworks += new TestFramework("verify.runner.Framework"),
mimaBinaryIssueFilters ++= Seq(
// this class is `private` and it's only used from `extractSemVerNumbers` method, which is private
ProblemFilters.exclude[MissingClassProblem]("sbtversionpolicy.DependencyCheckReport$SemVerVersion*")
// Add Mima filters here
),
)
Original file line number Diff line number Diff line change
@@ -1,42 +1,36 @@
package sbtversionpolicy

import coursier.version.{ModuleMatchers, Version, VersionCompatibility}
import dataclass.data
import lmcoursier.definitions.{ModuleMatchers => _, _}
import lmcoursier.definitions.{ModuleMatchers => *, *}

@data class DependencyCheckReport(
backwardStatuses: Map[(String, String), DependencyCheckReport.ModuleStatus],
forwardStatuses: Map[(String, String), DependencyCheckReport.ModuleStatus]
case class DependencyCheckReport(
compatibilityReports: Map[IncompatibilityType, Map[(String, String), DependencyCheckReport.ModuleStatus]]
) {
def validated(direction: Direction): Boolean =
(!direction.backward || backwardStatuses.forall(_._2.validated)) &&
(!direction.forward || forwardStatuses.forall(_._2.validated))

def errors(direction: Direction, ignored: Set[(String, String)] = Set.empty): (Seq[String], Seq[String]) = {
def validated(incompatibilityType: IncompatibilityType): Boolean =
compatibilityReports(incompatibilityType).forall(_._2.validated)

val backwardElems =
if (direction.backward) backwardStatuses else Map()
val forwardElems =
if (direction.forward) forwardStatuses else Map()
def errors(incompatibilityType: IncompatibilityType, ignored: Set[(String, String)] = Set.empty): (Seq[String], Seq[String]) = {

val baseErrors = (backwardElems.iterator.map((_, true)) ++ forwardElems.iterator.map((_, false)))
.filter(!_._1._2.validated)
val relevantErrors = compatibilityReports(incompatibilityType)

val baseErrors = relevantErrors
.filter(!_._2.validated)
.toVector
.sortBy(_._1._1)
.sortBy(_._1)

def message(org: String, name: String, backward: Boolean, status: DependencyCheckReport.ModuleStatus): String = {
val direction = if (backward) "backward" else "forward"
def message(org: String, name: String, status: DependencyCheckReport.ModuleStatus): String = {
s"$org:$name: ${status.message}"
}

val actualErrors = baseErrors.collect {
case ((orgName @ (org, name), status), backward) if !ignored(orgName) =>
message(org, name, backward, status)
case (orgName @ (org, name), status) if !ignored(orgName) =>
message(org, name, status)
}

val warnings = baseErrors.collect {
case ((orgName @ (org, name), status), backward) if ignored(orgName) =>
message(org, name, backward, status)
case (orgName @ (org, name), status) if ignored(orgName) =>
message(org, name, status)
}

(warnings, actualErrors)
Expand All @@ -48,49 +42,35 @@ object DependencyCheckReport {
sealed abstract class ModuleStatus(val validated: Boolean) extends Product with Serializable {
def message: String
}
@data class SameVersion(version: String) extends ModuleStatus(true) {
case class SameVersion(version: String) extends ModuleStatus(true) {
def message = s"found same version $version"
}
@data class CompatibleVersion(version: String, previousVersion: String, reconciliation: VersionCompatibility) extends ModuleStatus(true) {
case class CompatibleVersion(version: String, previousVersion: String, reconciliation: VersionCompatibility) extends ModuleStatus(true) {
def message = s"compatible version change from $previousVersion to $version (compatibility: ${reconciliation.name})"
}
@data class IncompatibleVersion(version: String, previousVersion: String, reconciliation: VersionCompatibility) extends ModuleStatus(false) {
case class IncompatibleVersion(version: String, previousVersion: String, reconciliation: VersionCompatibility) extends ModuleStatus(false) {
def message = s"incompatible version change from $previousVersion to $version (compatibility: ${reconciliation.name})"
}
@data class Missing(version: String) extends ModuleStatus(false) {
case class Missing(version: String) extends ModuleStatus(false) {
def message = "missing dependency"
}

private case class SemVerVersion(major: Int, minor: Int, patch: Int, suffix: Seq[Version.Item])

@deprecated("This method is internal.", "1.1.0")
def apply(
currentModules: Map[(String, String), String],
previousModules: Map[(String, String), String],
reconciliations: Seq[(ModuleMatchers, VersionCompatibility)],
defaultReconciliation: VersionCompatibility
): DependencyCheckReport =
apply(
Compatibility.BinaryCompatible,
currentModules,
previousModules,
reconciliations,
defaultReconciliation
)

private[sbtversionpolicy] def apply(
compatibilityIntention: Compatibility,
currentModules: Map[(String, String), String],
previousModules: Map[(String, String), String],
reconciliations: Seq[(ModuleMatchers, VersionCompatibility)],
defaultReconciliation: VersionCompatibility
): DependencyCheckReport = {

// FIXME These two lines compute the same result. What is the reason for having two directions?
val backward = moduleStatuses(compatibilityIntention, currentModules, previousModules, reconciliations, defaultReconciliation)
val forward = moduleStatuses(compatibilityIntention, currentModules, previousModules, reconciliations, defaultReconciliation)
def report(compatibility: Compatibility) =
moduleStatuses(compatibility, currentModules, previousModules, reconciliations, defaultReconciliation)

DependencyCheckReport(backward, forward)
DependencyCheckReport(Map(
IncompatibilityType.BinaryIncompatibility -> report(Compatibility.BinaryCompatible),
IncompatibilityType.SourceIncompatibility -> report(Compatibility.BinaryAndSourceCompatible)
))
}

@deprecated("This method is internal.", "1.1.0")
Expand Down Expand Up @@ -182,7 +162,7 @@ object DependencyCheckReport {
private def extractSemVerNumbers(versionString: String): Option[SemVerVersion] = {
val version = Version(versionString)
version.items match {
case Vector(major: Version.Number, minor: Version.Number, patch: Version.Number, suffix @ _*) =>
case Vector(major: Version.Number, minor: Version.Number, patch: Version.Number, suffix*) =>
Some(SemVerVersion(major.value, minor.value, patch.value, suffix))
case _ =>
None // Not a semantic version number (e.g., 1.0-RC1)
Expand Down
15 changes: 0 additions & 15 deletions sbt-version-policy/src/main/scala/sbtversionpolicy/Direction.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package sbtversionpolicy

/** Incompatibilities can be binary incompatibilities or
* source incompatibilities
*/
sealed trait IncompatibilityType

object IncompatibilityType {

case object BinaryIncompatibility extends IncompatibilityType
case object SourceIncompatibility extends IncompatibilityType

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.typesafe.tools.mima.core.Problem
import coursier.version.VersionCompatibility
import sbt.*
import sbt.librarymanagement.DependencyBuilders.OrganizationArtifactName
import sbtversionpolicy.internal.MimaIssues

import scala.util.matching.Regex

Expand All @@ -14,16 +13,13 @@ trait SbtVersionPolicyKeys {
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")
final val versionPolicyMimaCheck = taskKey[Unit]("Runs Mima to check backward or forward compatibility depending on the intended change defined via versionPolicyIntention.")
@deprecated("Use versionPolicyMimaCheck instead", "2.2.0")
final val versionPolicyForwardCompatibilityCheck = taskKey[Unit]("Report forward binary compatible issues from Mima.")
final val versionPolicyFindDependencyIssues = taskKey[Seq[(ModuleID, DependencyCheckReport)]]("Compatibility issues in the library dependencies.")
final def versionPolicyFindMimaIssues = TaskKey[Seq[(ModuleID, Seq[(MimaIssues.ProblemType, Problem)])]]("versionPolicyFindMimaIssues", "Binary or source compatibility issues over the previously released artifacts.")
final def versionPolicyFindIssues = TaskKey[Seq[(ModuleID, (DependencyCheckReport, Seq[(MimaIssues.ProblemType, Problem)]))]]("versionPolicyFindIssues", "Find both dependency issues and Mima issues.")
final def versionPolicyAssessCompatibility = TaskKey[Seq[(ModuleID, Compatibility)]]("versionPolicyAssessCompatibility", "Assess the compatibility level of the project compared to its previous releases.")
final val versionPolicyFindMimaIssues = taskKey[Seq[(ModuleID, Seq[(IncompatibilityType, Problem)])]]("Binary or source compatibility issues over the previously released artifacts.")
final val versionPolicyFindIssues = taskKey[Seq[(ModuleID, (DependencyCheckReport, Seq[(IncompatibilityType, Problem)]))]]("Find both dependency issues and Mima issues.")
final val versionPolicyAssessCompatibility = taskKey[Seq[(ModuleID, Compatibility)]]("Assess the compatibility level of the project compared to its previous releases.")
final val versionCheck = taskKey[Unit]("Checks that the version is consistent with the intended compatibility level defined via versionPolicyIntention")

final val versionPolicyIgnored = settingKey[Seq[OrganizationArtifactName]]("Exclude these dependencies from versionPolicyReportDependencyIssues.")
final val versionPolicyCheckDirection = settingKey[Direction]("Direction to check the version compatibility. Default: Direction.backward.")
// Note: defined as a def because adding a val to a trait is not binary compatible
final def versionPolicyIgnoredInternalDependencyVersions = SettingKey[Option[Regex]]("versionPolicyIgnoredInternalDependencyVersions", "Exclude dependencies to projects of the current build whose version matches this regular expression.")

Expand Down
Loading

0 comments on commit 75051c4

Please sign in to comment.