Skip to content

Commit 113fb03

Browse files
committed
Last pass
1 parent eedab49 commit 113fb03

File tree

1 file changed

+17
-13
lines changed

1 file changed

+17
-13
lines changed

blog/_posts/2021-02-16-preventing-version-conflicts-with-versionscheme.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ One of the things that makes Scala powerful and fun to use is its library ecosys
99

1010
However, the library ecosystem is not without problems. A library that you pulled could depend on other libraries, and the transitive dependencies could cause version conflicts. Here's a quick example of a Scala project that uses Akka HTTP, a Postgres database, and JSON. Its build declares two library dependencies, `akka-http-circe` and `doobie-postgres-circe`:
1111

12-
~~~ scala
12+
~~~
1313
libraryDependencies ++= Seq(
1414
"de.heikoseeberger" %% "akka-http-circe" % "1.26.0",
1515
"org.tpolecat" %% "doobie-postgres-circe" % "0.10.0"
@@ -22,7 +22,7 @@ sbt loads the project build, the project compiles successfully, but when you try
2222

2323
What happens here is that one of the transitive dependencies of `akka-http-circe` depends on `circe-core` version 0.11.1, and another transitive dependency of `doobie-postgres-circe` depends on `circe-core` version 0.13.0. Here's an excerpt of the output of sbt’s `dependencyTree` task:
2424

25-
~~~ text
25+
~~~
2626
[info] +-de.heikoseeberger:akka-http-circe_2.12:1.26.0 [S]
2727
[info] | +-io.circe:circe-core_2.12:0.11.1 (evicted by: 0.13.0)
2828
[info] |
@@ -32,15 +32,15 @@ What happens here is that one of the transitive dependencies of `akka-http-circe
3232

3333
Unfortunately, these two versions of Circe are not binary compatible. Such version conflicts are common in a dependency graph of any practical size. The problem is that we carefully program our code using a statically checked type system, but when it comes to production code we accept swapping out the JAR file with something that our dependency resolver like Coursier and Apache Ivy selected on a whim.
3434

35-
The Scala Center, Alexandre Archambault (author of Coursier), and Eugene Yokota have been working on a solution to improve this situation so that we can be confident about creating libraries, and using them.
35+
The Scala Center, Alexandre Archambault (author of Coursier), and Eugene Yokota have been working on a solution to reliably detect such conflicts at compilation time.
3636

3737
In the next section, we will explain the mechanism that was in place so far in sbt to address this issue, and we will discuss its limits. Then, we will introduce a new solution, which requires library authors to declare the versioning scheme of their libraries with a new sbt key, `versionScheme`.
3838

3939
## Eviction warnings
4040

4141
Actually this is not the first time we have thought of this issue. In 2014, we added [eviction warning][1] feature to sbt 0.13.6. When you have two candidate versions 0.11.1 and 0.13.0, and when it picks 0.13.0, 0.11.1 is said to be "evicted".
4242

43-
~~~ text
43+
~~~
4444
sbt:killer-app> evicted
4545
[warn] Found version conflict(s) in library dependencies; some are suspected to be binary incompatible:
4646
[warn] * io.circe:circe-core_2.12:0.13.0 is selected over 0.11.1
@@ -67,11 +67,11 @@ Given a version number `major.minor.patch`, you MUST increment the:
6767

6868
- When the `major` version is `0`, a minor version increment MAY contain **both source and binary breakages**, but a patch version increment MUST remain **binary compatible**.
6969

70-
We call this Early SemVer, because according to the [Semantic Versioning Spec][2] there are no guarantees between any versions when the major version is `0`. In the Scala library ecosystem, though, we often start guaranteeing binary compatibility for `0.y.z` like sbt 0.13 and Scala.js 0.6.
70+
We call this "Early" SemVer, because according to the [Semantic Versioning Spec][2] there are no guarantees between any versions when the major version is `0`. In the Scala library ecosystem, though, we often start guaranteeing binary compatibility for `0.y.z` like sbt 0.13 and Scala.js 0.6.
7171

7272
Unfortunately, it is not an easy task to know whether a change on public API broke source or binary compatibility. The Scala Center contracted Alexandre Archambault to create [sbt-version-policy][5]. This plugin helps library authors to self-check a version scheme. To use this, add the following to your `project/plugins.sbt`:
7373

74-
~~~ scala
74+
~~~
7575
addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "1.0.0-RC5")
7676
~~~
7777

@@ -82,12 +82,14 @@ And declare your compatibility intention regarding the next release, in your `bu
8282
ThisBuild / versionPolicyIntention := Compatibility.BinaryAndSourceCompatible
8383
~~~
8484

85-
This plugin provides a task called `versionPolicyCheck`, which you can call in the CI (continuous integration) server. The task performs an automatic binary compatibility check.
85+
This plugin provides a task called `versionPolicyCheck`, which you can call in the continuous integration server.
8686

87-
~~~ text
87+
~~~
8888
> versionPolicyCheck
8989
~~~
9090

91+
The task checks that the current state of the project is binary compatible with the previous release, and fails otherwise, so that you can prevent incompatibilities to be introduced in your project.
92+
9193
We think Early SemVer gives better flexibility to both library authors and library users since it gives more information about what would be in minor upgrades (we often call this feature release) vs patch. For example, sbt 1 ships bug fixes as 1.3.x patch releases without going through RC cycle, so bug fixes are released quickly.
9294

9395
During the minor upgrade (feature release), we aggressively add new features, while maintaining binary compatibility for the plugin ecosystem. These only come out once a year, and it goes through the RC cycle.
@@ -99,25 +101,25 @@ As mentioned earlier, sbt contains a built-in eviction warning feature, but ther
99101
Since sbt 1.4.0, there is a new setting `versionScheme`, which can be used by library authors
100102
to declare the versioning scheme they use:
101103

102-
~~~ scala
104+
~~~
103105
ThisBuild / versionScheme := Some("early-semver")
104106
~~~
105107

106108
sbt 1.4.0 includes this information into `pom.xml` and `ivy.xml` as a property. In addition, sbt uses the information to take the guessing out of eviction warning when this information is available.
107109

108-
In sbt 1.5.0, eviction warnings will be replaced with [eviction errors][8]. Since it can now reliably detect whether two dependencies with different versions are compatible, or if they conflict, the build will fail if an incompatibility is detected in your dependencies.
110+
In sbt 1.5.0, eviction warnings will be replaced with [eviction errors][8]. Since it can now reliably detect whether two dependencies with different versions are compatible, or if they conflict, the build will fail if an incompatibility is detected in your dependencies. This is of course possible only if the libraries provide their `versionScheme`, otherwise sbt will keep issuing eviction warnings.
109111

110112
It might take a few years for the `versionScheme` information to become prevalent in the ecosystem. In the meantime, as a user of libraries you can manually configure the versioning scheme used by your libraries by using a new setting, `libraryDependencySchemes`. For instance, here is how you can tell sbt that the `circe-core` artifact follows the Early SemVer scheme:
111113

112-
~~~ scala
114+
~~~
113115
ThisBuild / libraryDependencySchemes += "io.circe" %% "circe-core" % "early-semver"
114116
~~~
115117

116118
With this setting, the warning caused by the conflicting versions of circe-core
117119
becomes an error because the version number 0.13.0 is not binary compatible
118120
with the version number 0.11.1 according to the Early SemVer scheme:
119121

120-
~~~ text
122+
~~~
121123
sbt:killer-app> update
122124
[error] stack trace is suppressed; run last update for the full output
123125
[error] (update) found version conflict(s) in library dependencies; some are suspected to be binary incompatible:
@@ -141,7 +143,9 @@ to implement the versioning scheme that you declare in your build, by detecting
141143
and source incompatibilities introduced between releases.
142144

143145
As a library user, starting from sbt 1.5.0 you should configure your
144-
`libraryDependencySchemes` to get accurate eviction errors.
146+
`libraryDependencySchemes` to get accurate eviction errors for libraries that don’t (yet) provide their `versionScheme`.
147+
148+
_[Eugene Yokota](https://twitter.com/eed3si9n) is the lead developer of sbt. [Julien Richard-Foy](https://twitter.com/julienrf) makes MOOCs at the Scala Center_.
145149

146150
[1]: https://github.com/sbt/sbt/pull/147
147151
[2]: https://semver.org/

0 commit comments

Comments
 (0)