diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala index 7e4c118..6d9caf2 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala @@ -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 + } + } diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala index 47de24f..03961c9 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala @@ -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 + } + } + } + } + } diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/build.sbt b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/build.sbt new file mode 100644 index 0000000..b9215a5 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/build.sbt @@ -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) + + diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/project/plugins.sbt b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/project/plugins.sbt new file mode 100644 index 0000000..2843375 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % sys.props("plugin.version")) diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/test b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/test new file mode 100644 index 0000000..54dbb27 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/test @@ -0,0 +1,7 @@ +> v1_root/publishLocal + +> v2_root/check + +> v3_root/check + +> v4_root/check diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v1_a/src/main/scala/pkg/A.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v1_a/src/main/scala/pkg/A.scala new file mode 100644 index 0000000..e6c552f --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v1_a/src/main/scala/pkg/A.scala @@ -0,0 +1,7 @@ +package pkg + +object A { + + val x: Int = 42 + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v1_b/src/main/scala/pkg/B.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v1_b/src/main/scala/pkg/B.scala new file mode 100644 index 0000000..b20f43c --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v1_b/src/main/scala/pkg/B.scala @@ -0,0 +1,5 @@ +package pkg + +object B { + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v2_a/src/main/scala/pkg/A.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v2_a/src/main/scala/pkg/A.scala new file mode 100644 index 0000000..e6c552f --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v2_a/src/main/scala/pkg/A.scala @@ -0,0 +1,7 @@ +package pkg + +object A { + + val x: Int = 42 + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v2_b/src/main/scala/pkg/B.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v2_b/src/main/scala/pkg/B.scala new file mode 100644 index 0000000..cb04c96 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v2_b/src/main/scala/pkg/B.scala @@ -0,0 +1,7 @@ +package pkg + +object B { + + private val b: String = "b" + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v3_a/src/main/scala/pkg/A.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v3_a/src/main/scala/pkg/A.scala new file mode 100644 index 0000000..6fa03a9 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v3_a/src/main/scala/pkg/A.scala @@ -0,0 +1,9 @@ +package pkg + +object A { + + val x: Int = 42 + + val y: Int = 0 + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v3_b/src/main/scala/pkg/B.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v3_b/src/main/scala/pkg/B.scala new file mode 100644 index 0000000..cb04c96 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v3_b/src/main/scala/pkg/B.scala @@ -0,0 +1,7 @@ +package pkg + +object B { + + private val b: String = "b" + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v4_a/src/main/scala/pkg/A.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v4_a/src/main/scala/pkg/A.scala new file mode 100644 index 0000000..6fa03a9 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v4_a/src/main/scala/pkg/A.scala @@ -0,0 +1,9 @@ +package pkg + +object A { + + val x: Int = 42 + + val y: Int = 0 + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v4_b/src/main/scala/pkg/B.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v4_b/src/main/scala/pkg/B.scala new file mode 100644 index 0000000..9659614 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/aggregate-assessed-compatibility/v4_b/src/main/scala/pkg/B.scala @@ -0,0 +1,3 @@ +package pkg + +object C