Skip to content

Conversation

eed3si9n
Copy link
Member

@eed3si9n eed3si9n commented Dec 21, 2020

Fixes #5976
Ref sbt/librarymanagement#356
Ref https://eed3si9n.com/enforcing-semver-with-sbt-strict-update

This removes the guess-based EvictionWarning, and runs EvictionError instead.
EvictionError uses the ThisBuild / versionScheme information supplied by the library authors in addition to libraryDependencySchemes that the build users could provide:

ThisBuild / libraryDependencySchemes += "com.example" %% "name" % "early-semver"

as the version scheme "early-semver", "semver-spec", "pvp", "strict", or "always" may be used.
Here's an example of update failure:

[error]  * org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.2.0, 2.0.0, 2.0.0, 2.2.0}
[error]      +- com.example:use2_2.13:0.1.0-SNAPSHOT               (depends on 3.0.0-M4)
[error]      +- org.http4s:http4s-core_2.13:0.21.11                (depends on 2.2.0)
[error]      +- io.chrisdavenport:vault_2.13:2.0.0                 (depends on 2.0.0)
[error]      +- io.chrisdavenport:unique_2.13:2.0.0                (depends on 2.0.0)
[error]      +- co.fs2:fs2-core_2.13:2.4.5                         (depends on 2.2.0)
[error]
[error]
[error] this can be overridden using libraryDependencySchemes or evictionErrorLevel

This feature will catch the violation of cats-effect_2.13:3.0.0-M4 version scheme (early-semver) without user setting additional overrides. If the user wants to opt-out of this, the user can do so per module:

ThisBuild / libraryDependencySchemes += "org.typelevel" %% "cats-effect" % "always"

or globally as:

ThisBuild / evictionErrorLevel := Level.Info

credits

The idea of supplying version scheme information via the build, and making eviction into an error was first implemented in https://github.com/scalacenter/sbt-eviction-rules by @alexarchambault and @julienrf at Scala Center.

Fixes sbt#5976
Ref https://eed3si9n.com/enforcing-semver-with-sbt-strict-update

This removes the guess-based EvictionWarning, and runs EvictionError instead.
EvictionError uses the `ThisBuild / versionScheme` information supplied by the library authors in addition to `libraryDependencySchemes` that the build users could provide:

```scala
ThisBuild / libraryDependencySchemes += "com.example" %% "name" % "early-semver"
```

as the version scheme "early-semver", "semver-spec", "pvp", "strict", or "always" may be used.
Here's an example of `update` failure:

```
[error]  * org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.2.0, 2.0.0, 2.0.0, 2.2.0}
[error]      +- com.example:use2_2.13:0.1.0-SNAPSHOT               (depends on 3.0.0-M4)
[error]      +- org.http4s:http4s-core_2.13:0.21.11                (depends on 2.2.0)
[error]      +- io.chrisdavenport:vault_2.13:2.0.0                 (depends on 2.0.0)
[error]      +- io.chrisdavenport:unique_2.13:2.0.0                (depends on 2.0.0)
[error]      +- co.fs2:fs2-core_2.13:2.4.5                         (depends on 2.2.0)
[error]
[error]
[error] this can be overridden using libraryDependencySchemes or evictionErrorLevel
```

This catches the violation of cats-effect_2.13:3.0.0-M4 version scheme (early-semver) without user setting additional overrides. If the user wants to opt-out of this, the user can do so per module:

```scala
ThisBuild / libraryDependencySchemes += "org.typelevel" %% "cats-effect" % "always"
```

or globally as:

```
ThisBuild / evictionErrorLevel := Level.Info
```
@julienrf
Copy link
Contributor

EvictionError uses the ThisBuild / versionScheme information supplied by the library authors

What is the default versionScheme, if the library does not provide it? Is it still pvp?

@eed3si9n
Copy link
Member Author

What is the default versionScheme, if the library does not provide it? Is it still pvp?

By default it assumes to work, so same as "always"

@julienrf
Copy link
Contributor

julienrf commented Feb 9, 2021

@eed3si9n, as prompted by @majk-p in scalacenter/sbt-eviction-rules#22 (comment), I’m thinking that the new behavior might cause some troubles. What do you think of the alternative proposed there instead?

So, maybe the best thing would be to fall back to what sbt 1.4 was resolving to in that case, and to print a warning explaining that sbt could not do its job because versionScheme is not set by the library.

@majk-p
Copy link

majk-p commented Feb 9, 2021

If it's printing warning instead of raising errors preventing compilation - I think this will be most intuitive and consistent with current user experience.

In current shape of this feature - I'm afraid not many developers are aware of the impact of the change. Some extensively-used libraries like Cats don't publish the versionScheme (typelevel/cats#3780).

@eed3si9n
Copy link
Member Author

eed3si9n commented Feb 9, 2021

I want to get rid of any guessing, and be 100% sure if we're displaying warnings or errors. Eviction warning failed as implementation because it had too many false positives.

If people want to have more version scheme information, maybe someone could start a third-party libraryDependencySchemes plugin? I'm guessing there's 80:20, like 20 libraries cover 80% of the ecosystem, or something like that.

@majk-p
Copy link

majk-p commented Feb 9, 2021

I totally agree with @eed3si9n that warnings didn't succeed as people would ignore them, often without uunderstanding.
We are definitely having a problem of missing information about versioning scheme.
I'm just concerned that instead of solving it, with this feature we are shifting the responsibility of guessing from SBT to the developers.

I'm worried this might negatively affect Scala as a language with SBT being the dominant tool most newcommers have to use. The learning path is difficult enough, if I was a newbie and apart from wha I have to learn already I had to decide about how others version their libraries - I wouldn't be convinced I want to use this kind of ecosystem.

Introducing libraryDependencySchemes plugin wpuld definitely help, but then wouldn't we enter a trap of a plugin that forces certain versioning scheme on somebody else's library?

I think we should think of solutions that might shift the responsibility to people who know best how versioning is defined - the library developers. This of course would require their involvment before releasing feature like eviction errors.
What do you think about such approach?

@eed3si9n
Copy link
Member Author

eed3si9n commented Feb 9, 2021

Library authors can opt into this scheme by publishing with ThisBuild / versionScheme, that's how we're shifting the responsibility to the library developers. Everything else is gravy for advanced users. I'm not sure what more we can do here. I don't think eviction warning going away is suddenly going to make sbt more or less friendly to the new users.

@julienrf
Copy link
Contributor

julienrf commented Feb 9, 2021

I want to get rid of any guessing, and be 100% sure if we're displaying warnings or errors

I agree but the only way to get rid of guessing is to rely on versionScheme (assuming library authors do implement the versioning scheme they claim). If I understand correctly, the new behavior will silently use always when versionScheme is absent, which is worse than a false positive eviction warning, IMHO.

So, the only case where it is non-controversial to emit an error, in my opinion, is when versionScheme is set by the library (or by the libraryDependencySchemes key, in the end-user project). In the other cases, I think it would be more reasonable to keep the previous behavior (ie, proceed to evictions based on pvp and emit a warning), but with a more informative warning like “Unable to resolve the conflict between versions [1.1.0, 1.2.0] of some-org:some-artifact. This could be solved by setting libraryDependencySchemes in this project, or by setting versionScheme in the artifact itself.”

That being said, I agree that the responsibility should be shifted to the developers, not to the end-users. Would it be possible to emit a warning when developers try to publish something without defining a versionScheme?

@kubukoz
Copy link
Contributor

kubukoz commented Feb 9, 2021

I don't think eviction warning going away is suddenly going to make sbt more or less friendly to the new users.

New/existing users alike will be impacted by this change. I've been using sbt for years and I'd happily ignore the missing evictions (after all, if there are no warnings, that's surely good, right?) if I didn't know this change was coming.
I know many people will also not be concerned if they don't see these warnings...

and for the new users specifically, not having these warnings will mean having one fewer clue in the search for a possible cause of something like e.g. a NoSuchMethodError.

I believe that, in order to avoid surprising and potentially dangerous behavior, it would be best to go with one of these approaches:

  • keeping the existing behavior for libraries that don't configure this (showing warnings)
  • being even more defensive - failing the build if there is an unconfigured library, maybe with an escape hatch (a flag that you can set to fallback to warnings).

The first option is probably more reasonable. The behavior could be temporary, and phased out in e.g. 1.6 or 1.7, to give users a chance to adjust to the new behavior without the sudden loss of valuable information.

That being said, I agree that the responsibility should be shifted to the developers, not to the end-users. Would it be possible to emit a warning when developers try to publish something without defining a versionScheme?

This still requires everyone to publish. We most definitely can't expect everyone to publish their libraries before users start updating their build to sbt 1.5.0.

@eed3si9n
Copy link
Member Author

eed3si9n commented Feb 9, 2021

If we want to advocate for the merits of the eviction warning, maybe a better issue to do so is #5976, which this PR fixed by removing the warnings. Since I originally created them, only thing I've been hearing consistently is that how broken eviction warnings are so I'm a bit surprised that you think keeping the warnings would make sbt user-friendly now that it will be gone.

@kubukoz
Copy link
Contributor

kubukoz commented Feb 9, 2021

In dire situations, even poor feedback is better than no feedback at all ;)

@julienrf
Copy link
Contributor

julienrf commented Feb 10, 2021

Since I originally created them, only thing I've been hearing consistently is that how broken eviction warnings are so I'm a bit surprised that you think keeping the warnings would make sbt user-friendly now that it will be gone.

I understand your concerns, let me clarify my opinion. What I think is problematic is not much to not warn anymore but to change the behavior of sbt. I think it’s better to keep showing a warning if there is a risk of conflict. Furthermore, we now have an opportunity to improve the warning to say that it could be fixed if versionScheme or libraryDependencySchemes were set (which would contribute to incentivize maintainers to set their versionScheme).

That doesn’t solve the problem of the existing libraries, though, as Jakub noticed. So, maybe we also need to define a list of libraryDependencySchemes somewhere for the libraries that are used in 80% of the projects?

@jtjeferreira
Copy link
Contributor

Just my 2 cents, from my experience in work project:

Regarding the example in the description about cats-effect 2 vs 3. As maintainer of applications
having that error would be nice, but I want to know of all the the changes in dependencies even if it was a change from cats-effect 2.0.0 to 2.2.0, instead of automatically being upgraded to 2.2.0.

The disadvantage of using sbt-lock is that it uses dependencyOverrides to force the versions, and sbt/coursier still have to "solve" dependencies. If this was included in sbt/coursier with a proper lock file sbt/coursier just would have to download things and maybe check checksums...

(Sorry if I am taking the discussion to a different direction, but I just wanted to share that)

@majk-p
Copy link

majk-p commented Feb 10, 2021

I've mentioned it in the original issue (scalacenter/sbt-eviction-rules#22) and I'd like to clarify that in general I like the idea of eviction errors.
There's already some great feedback about how people feel about the change and what could be improved.

Talking about solutions now, this is what I think could be introduced to address most of the issues

  1. Let users decide about default versionScheme for libraries that don't define it
    Currently the only thing user can do is
evictionRules += "*" %% "*" % "pvp"

But this effectively overrides all libraries that match, even if they do provide versionScheme. I'd suggest introducing something like

defaultEvictionRule := "pvp"

With "always" as the default value

  1. Allow users to decide if they want to turn on warnings
    In case some users didn't ignore the warnings and belive those were useful to them, I'd vote for allowing them to enable warnings instead of silently evicting with an sbt flag like onEvictionByDefaultVersionScheme := "warn" (fail|warn|info)

Given the "info" as the default value, points 1 and 2 effectively lead to the same behavior as with current candidate for 1.5.0, while giving the elasticity to fall back to previous behavior with

defaultEvictionRule := "pvp"
onEvictionByDefaultVersionScheme := "warn"
  1. Also referring to @julienrf

Would it be possible to emit a warning when developers try to publish something without defining a versionScheme?

If it was possible, SBT could start emitting a warning that the library is missing the versionScheme. (perhaps this field could even become mandatory with some default value in future?)

Those don't really address the sbt-lock idea, but while it's interesting, seems like a larger topic to discuss separately.

@matwojcik
Copy link

My 2 cents - I'd be against failing the build due to any other factor than explicit versioning scheme defined by library author (or overrides in current build) - that's why defaulting to pvp and failing on eviction because of that is IMHO poor user experience - too many false results would be there.

Warnings on defaults - they don't bother me -I get it that people rarely look at them, but for those who do, they can help.

Ideal situation would be if all of that was configurable like @majk-p suggested, with some reasonable defaults.

@julienrf
Copy link
Contributor

@jtjeferreira Interesting, I didn’t know about sbt-lock! However, I must say that its usage seems to be more tedious than what I hope we will get with reliable eviction errors provided by versionScheme/libraryDependencySchemes, since you have to deal with this large lock.sbt file.

Regarding the example in the description about cats-effect 2 vs 3. As maintainer of applications
having that error would be nice, but I want to know of all the changes in dependencies even if it was a change from cats-effect 2.0.0 to 2.2.0, instead of automatically being upgraded to 2.2.0.

The plugin sbt-version-policy does enforce that you bump the major version of your library if you bumped the major version of one of your library dependencies. So, if cats-effect uses sbt-version-policy, a change from 2.0.0 to 2.2.0 means that the dependencies of cats-effects have only been bumped in their minor version too (no major bump). (Not sure if I’m clear!)

@jtjeferreira
Copy link
Contributor

@jtjeferreira Interesting, I didn’t know about sbt-lock!

yeah I dont think it is used widely.

since you have to deal with this large lock.sbt file.

I am not sure what "have to deal with this large lock.sbt" exactly means, because this file is autogenerated (using lock task), and I just need to commit it to git and make sure it is regenerated when some1 makes some update (as I said I use CI and task checkLockUpdate to verify that)

The plugin sbt-version-policy does enforce that you bump the major version of your library if you bumped the major version of one of your library dependencies. So, if cats-effect uses sbt-version-policy, a change from 2.0.0 to 2.2.0 means that the dependencies of cats-effects have only been bumped in their minor version too (no major bump). (Not sure if I’m clear!)

I think you were clear, but as far as I understand sbt-version-policy is more suited for libraries. For an application I dont care about source or binary dependencies. I just care about unwanted libraries upgrades that result in ClassNotFound or MethodNotFound

PS: I like @majk-p proposal

@majk-p
Copy link

majk-p commented Feb 11, 2021

So what should be the next steps? Any conclusions from this discussion?

@eed3si9n
Copy link
Member Author

@majk-p I've opened issues based on your proposal.

@majk-p
Copy link

majk-p commented Feb 11, 2021

Thank you!

@julienrf
Copy link
Contributor

Thanks Eugene. Can we add these issues to the 1.5.0 milestone?

rtyley added a commit to scanamo/scanamo that referenced this pull request Jul 11, 2021
The new, stricter eviction logic in sbt v1.5 (see
sbt/sbt#6221) spotted that the version of
`cats-effect` used by Scanamo were suspected to be binary incompatible:

```
[error] (zio / update) found version conflict(s) in library dependencies; some are suspected to be binary incompatible:
[error]
[error] 	* org.typelevel:cats-effect_2.12:3.1.1 (early-semver) is selected over 2.5.1
[error] 	    +- org.scanamo:scanamo-zio_2.12:1.0.0-M15+101-13c581a6-SNAPSHOT (depends on 3.1.1)
[error] 	    +- co.fs2:fs2-core_2.12:3.0.3                         (depends on 3.1.1)
[error] 	    +- org.typelevel:cats-effect_2.12:2.5.1               (depends on 2.5.1)
[error]
[error]
[error] this can be overridden using libraryDependencySchemes or evictionErrorLevel

```

Upgrading to use cats-effect v3.1.1 for consistency meant:

* Using `async_` rather than `async` (which has an updated method
  signature that supports cancellation) - see
  https://typelevel.org/cats-effect/docs/migration-guide#async
* Using `cats.effect.unsafe.implicits.global` in tests, where no
  `IORuntime` was available - an alternative way to do this would be
  using https://github.com/typelevel/cats-effect-testing/#scalatest
  See also https://typelevel.org/cats-effect/docs/migration-guide#io
rtyley added a commit to scanamo/scanamo that referenced this pull request Jul 11, 2021
The changes required by this update are:

* Removing use of the deprecated sbt-0.13-style-DSL `in` syntax, see
  https://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+to+slash+syntax
* Tweaks for sbt v1.5 eviction errors (see
  sbt/sbt#6221):
  * Upgrade `cats-effect` to v3.1.1 - see
    https://github.com/scanamo/scanamo/pull/1166PR
  * Set `evictionErrorLevel := Level.Info`, because while we can fix
    `cats-effect`, we can't currently fix `akka-http` - the current latest
    version of `akka-http` suffers from a semver issue with
    `scala-java8-compat` - see akka/akka#30375

```
[info] 	* org.scala-lang.modules:scala-java8-compat_2.12:1.0.0 (early-semver) is selected over 0.8.0
[info] 	    +- org.scanamo:scanamo_2.12:1.0.0-M15+99-399e98af+20210710-2246-SNAPSHOT (depends on 1.0.0)
[info] 	    +- com.typesafe.akka:akka-actor_2.12:2.5.31           (depends on 0.8.0)
```
@adrian-2414745
Copy link

Is it normal to not find the dependencies from the update failure in the dependency tree ? How do I know where they are coming from? because they are not in the dependencyTree which is printed

@julienrf
Copy link
Contributor

I think dependencyTree requires a resolved dependency tree, so if there is a conflict we can’t see a proper dependencyTree.

I think we can address your issue @adrian-salajan by showing a more complete dependency chain when sbt exhibits the conflicting dependencies. Currently, it only shows the conflicting modules, but sometimes these modules come from transitive dependencies, and it’s not easy to figure out where they come from. Instead, sbt could show the complete chain of dependencies that eventually pulled the conflicting modules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove eviction warnings
7 participants