Skip to content

Commit

Permalink
Add a convenient method to compute the highest level of compatibility…
Browse files Browse the repository at this point in the history
… of all the sub-projects aggregated by a project
  • Loading branch information
julienrf committed Nov 29, 2023
1 parent fef2946 commit 9c3dded
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,12 @@ object Compatibility {
}
}

// Ordering from the least compatible to the most compatible
implicit val ordering: Ordering[Compatibility] =
Ordering.by {
case Compatibility.None => 0
case Compatibility.BinaryCompatible => 1
case Compatibility.BinaryAndSourceCompatible => 3
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,40 @@ object SbtVersionPolicyPlugin extends AutoPlugin {
SbtVersionPolicySettings.findIssuesSettings ++
SbtVersionPolicySettings.skipSettings

/**
* Compute the highest compatibility level satisfied by all the projects aggregated by the
* project this task is applied to.
* This is useful to know the overall level of compatibility of a multi-module project.
* It invokes `versionPolicyAssessCompatibility` on all the aggregated projects and keeps
* the first result only (ie, it assumes that that tasks assessed the compatibility with
* the latest release only).
*/
val aggregatedAssessedCompatibilityWithLatestRelease: Def.Initialize[Task[Compatibility]] =
Def.taskDyn {
import autoImport.versionPolicyAssessCompatibility
val log = Keys.streams.value.log
// Take all the projects aggregated by this project
val aggregatedProjects = Keys.thisProject.value.aggregate

// Compute the highest compatibility level that is satisfied by all the aggregated projects
val maxCompatibility: Compatibility = Compatibility.BinaryAndSourceCompatible
aggregatedProjects.foldLeft(Def.task { maxCompatibility }) { (highestCompatibilityTask, project) =>
Def.task {
val highestCompatibility = highestCompatibilityTask.value
val compatibilities = (project / versionPolicyAssessCompatibility).value
// The most common case is to assess the compatibility with the latest release,
// so we look at the first element only and discard the others
compatibilities.headOption match {
case Some((_, compatibility)) =>
log.debug(s"Compatibility of aggregated project ${project.project} is ${compatibility}")
// Take the lowest of both
Compatibility.ordering.min(highestCompatibility, compatibility)
case None =>
log.debug(s"Unable to assess the compatibility level of the aggregated project ${project.project}")
highestCompatibility
}
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// A project with two modules (“a” and “b”) and a root module that aggregates them

val v1_a =
project
.settings(
name := "aggregated-test-a",
version := "1.0.0",
)

val v1_b =
project
.settings(
name := "aggregated-test-b",
version := "1.0.0",
)

val v1_root =
project
.settings(
name := "aggregated-test-root",
publish / skip := true,
)
.aggregate(v1_a, v1_b)


// First round of evolutions
// No changes in v2_a
val v2_a =
project
.settings(
name := "aggregated-test-a",
version := "1.0.0+n",
)

// Small changes that don’t break the binary and source compatibility
val v2_b =
project
.settings(
name := "aggregated-test-b",
version := "1.0.0+n",
)

val v2_root =
project
.settings(
name := "aggregated-test-root",
publish / skip := true,
TaskKey[Unit]("check") := {
val compatibility = SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease.value
assert(compatibility == Compatibility.BinaryAndSourceCompatible)
}
)
.aggregate(v2_a, v2_b)


// Second round of evolutions
// Introduction of a public member
val v3_a =
project
.settings(
name := "aggregated-test-a",
version := "1.0.0+n",
)

// No changes
val v3_b =
project
.settings(
name := "aggregated-test-b",
version := "1.0.0+n",
)

val v3_root =
project
.settings(
name := "aggregated-test-root",
publish / skip := true,
TaskKey[Unit]("check") := {
val compatibility = SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease.value
assert(compatibility == Compatibility.BinaryCompatible)
}
)
.aggregate(v3_a, v3_b)

// Third round of evolutions
// Introduction of a public member
val v4_a =
project
.settings(
name := "aggregated-test-a",
version := "1.0.0+n",
)

// Removal of a public member
val v4_b =
project
.settings(
name := "aggregated-test-b",
version := "1.0.0+n",
)

val v4_root =
project
.settings(
name := "aggregated-test-root",
publish / skip := true,
TaskKey[Unit]("check") := {
val compatibility = SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease.value
assert(compatibility == Compatibility.None)
}
)
.aggregate(v4_a, v4_b)


Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % sys.props("plugin.version"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
> v1_root/publishLocal

> v2_root/check

> v3_root/check

> v4_root/check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pkg

object A {

val x: Int = 42

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pkg

object B {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pkg

object A {

val x: Int = 42

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pkg

object B {

private val b: String = "b"

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pkg

object A {

val x: Int = 42

val y: Int = 0

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pkg

object B {

private val b: String = "b"

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pkg

object A {

val x: Int = 42

val y: Int = 0

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pkg

object C

0 comments on commit 9c3dded

Please sign in to comment.