Skip to content

Commit eebc935

Browse files
committed
Add a convenient method to compute the highest level of compatibility of all the sub-projects aggregated by a project
1 parent 9f5ab90 commit eebc935

File tree

13 files changed

+220
-0
lines changed

13 files changed

+220
-0
lines changed

sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,12 @@ object Compatibility {
9090
}
9191
}
9292

93+
// Ordering from the least compatible to the most compatible
94+
implicit val ordering: Ordering[Compatibility] =
95+
Ordering.by {
96+
case Compatibility.None => 0
97+
case Compatibility.BinaryCompatible => 1
98+
case Compatibility.BinaryAndSourceCompatible => 3
99+
}
100+
93101
}

sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,40 @@ object SbtVersionPolicyPlugin extends AutoPlugin {
3535
SbtVersionPolicySettings.findIssuesSettings ++
3636
SbtVersionPolicySettings.skipSettings
3737

38+
/**
39+
* Compute the highest compatibility level satisfied by all the projects aggregated by the
40+
* project this task is applied to.
41+
* This is useful to know the overall level of compatibility of a multi-module project.
42+
* On every aggregated project, it invokes `versionPolicyAssessCompatibility` and keeps
43+
* the first result only (ie, it assumes that that task assessed the compatibility with
44+
* the latest release only).
45+
*/
46+
val aggregatedAssessedCompatibilityWithLatestRelease: Def.Initialize[Task[Compatibility]] =
47+
Def.taskDyn {
48+
import autoImport.versionPolicyAssessCompatibility
49+
val log = Keys.streams.value.log
50+
// Take all the projects aggregated by this project
51+
val aggregatedProjects = Keys.thisProject.value.aggregate
52+
53+
// Compute the highest compatibility level that is satisfied by all the aggregated projects
54+
val maxCompatibility: Compatibility = Compatibility.BinaryAndSourceCompatible
55+
aggregatedProjects.foldLeft(Def.task { maxCompatibility }) { (highestCompatibilityTask, project) =>
56+
Def.task {
57+
val highestCompatibility = highestCompatibilityTask.value
58+
val compatibilities = (project / versionPolicyAssessCompatibility).value
59+
// The most common case is to assess the compatibility with the latest release,
60+
// so we look at the first element only and discard the others
61+
compatibilities.headOption match {
62+
case Some((_, compatibility)) =>
63+
log.debug(s"Compatibility of aggregated project ${project.project} is ${compatibility}")
64+
// Take the lowest of both
65+
Compatibility.ordering.min(highestCompatibility, compatibility)
66+
case None =>
67+
log.debug(s"Unable to assess the compatibility level of the aggregated project ${project.project}")
68+
highestCompatibility
69+
}
70+
}
71+
}
72+
}
73+
3874
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// A project with two modules (“a” and “b”) and a root module that aggregates them
2+
3+
val v1_a =
4+
project
5+
.settings(
6+
name := "aggregated-test-a",
7+
version := "1.0.0",
8+
)
9+
10+
val v1_b =
11+
project
12+
.settings(
13+
name := "aggregated-test-b",
14+
version := "1.0.0",
15+
)
16+
17+
val v1_root =
18+
project
19+
.settings(
20+
name := "aggregated-test-root",
21+
publish / skip := true,
22+
)
23+
.aggregate(v1_a, v1_b)
24+
25+
26+
// First round of evolutions
27+
// No changes in v2_a
28+
val v2_a =
29+
project
30+
.settings(
31+
name := "aggregated-test-a",
32+
version := "1.0.0+n",
33+
)
34+
35+
// Small changes that don’t break the binary and source compatibility
36+
val v2_b =
37+
project
38+
.settings(
39+
name := "aggregated-test-b",
40+
version := "1.0.0+n",
41+
)
42+
43+
val v2_root =
44+
project
45+
.settings(
46+
name := "aggregated-test-root",
47+
publish / skip := true,
48+
TaskKey[Unit]("check") := {
49+
val compatibility = SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease.value
50+
assert(compatibility == Compatibility.BinaryAndSourceCompatible)
51+
}
52+
)
53+
.aggregate(v2_a, v2_b)
54+
55+
56+
// Second round of evolutions
57+
// Introduction of a public member
58+
val v3_a =
59+
project
60+
.settings(
61+
name := "aggregated-test-a",
62+
version := "1.0.0+n",
63+
)
64+
65+
// No changes
66+
val v3_b =
67+
project
68+
.settings(
69+
name := "aggregated-test-b",
70+
version := "1.0.0+n",
71+
)
72+
73+
val v3_root =
74+
project
75+
.settings(
76+
name := "aggregated-test-root",
77+
publish / skip := true,
78+
TaskKey[Unit]("check") := {
79+
val compatibility = SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease.value
80+
assert(compatibility == Compatibility.BinaryCompatible)
81+
}
82+
)
83+
.aggregate(v3_a, v3_b)
84+
85+
// Third round of evolutions
86+
// Introduction of a public member
87+
val v4_a =
88+
project
89+
.settings(
90+
name := "aggregated-test-a",
91+
version := "1.0.0+n",
92+
)
93+
94+
// Removal of a public member
95+
val v4_b =
96+
project
97+
.settings(
98+
name := "aggregated-test-b",
99+
version := "1.0.0+n",
100+
)
101+
102+
val v4_root =
103+
project
104+
.settings(
105+
name := "aggregated-test-root",
106+
publish / skip := true,
107+
TaskKey[Unit]("check") := {
108+
val compatibility = SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease.value
109+
assert(compatibility == Compatibility.None)
110+
}
111+
)
112+
.aggregate(v4_a, v4_b)
113+
114+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % sys.props("plugin.version"))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
> v1_root/publishLocal
2+
3+
> v2_root/check
4+
5+
> v3_root/check
6+
7+
> v4_root/check
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pkg
2+
3+
object A {
4+
5+
val x: Int = 42
6+
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package pkg
2+
3+
object B {
4+
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pkg
2+
3+
object A {
4+
5+
val x: Int = 42
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pkg
2+
3+
object B {
4+
5+
private val b: String = "b"
6+
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package pkg
2+
3+
object A {
4+
5+
val x: Int = 42
6+
7+
val y: Int = 0
8+
9+
}

0 commit comments

Comments
 (0)