Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maven version range should translate to a fixed lower bound in Ivy #2954

Open
eed3si9n opened this issue Feb 6, 2017 · 3 comments
Open

Maven version range should translate to a fixed lower bound in Ivy #2954

eed3si9n opened this issue Feb 6, 2017 · 3 comments
Assignees
Labels
area/library_management library management
Milestone

Comments

@eed3si9n
Copy link
Member

eed3si9n commented Feb 6, 2017

This is spawn from #2291 (comment)

Upon a dependency conflict (multiple version candidates are found for the same module), by default Ivy uses latest-wins, and Maven uses nearest-wins logic to pick the winner. Given that most people use this default, it might make sense to simply use the lower bound instead of trying to translate Maven's version range to Ivy's dynamic revision.

case study

See for example: #2291

libraryDependencies ++= Seq(
   "org.webjars.bower" % "angular" % "1.4.7",
  "org.webjars.bower" % "angular-bootstrap" % "0.14.2"
)

angular-bootstrap requires:

        <dependency>
            <groupId>org.webjars.bower</groupId>
            <artifactId>angular</artifactId>
            <version>[1.3.0,)</version>
        </dependency>

If we translate Maven's [1.3.0,) to Ivy's dynamic revision, Ivy will go out to the Internet and find 1.5.0-beta.2. This is surprising because the user has already specified 1.4.7 in the graph that satisfies the condition.

If instead as I propose here we translated it to the lower bound 1.3.0, Ivy will evict it and pick 1.4.7 as expected.

@eed3si9n eed3si9n added the area/library_management library management label Feb 6, 2017
@eed3si9n eed3si9n added this to the 0.13.14 milestone Feb 8, 2017
eed3si9n added a commit to eed3si9n/sbt that referenced this issue Feb 10, 2017
Previously, when the dependency resolver (Ivy) encountered a Maven version range such as `[1.3.0,)`
it would go out to the Internet to find the latest version.
This would result to a surprising behavior where the eventual version keeps changing over time
*even when there's a version of the library that satisfies the range condition*.

This changes to some Maven version ranges would be replaced with its lower bound
so that when a satisfactory version is found in the dependency graph it will be used.
You can disable this behavior using the JVM flag `-Dsbt.modversionrange=false`.

Fixes sbt#2954
Ref sbt#2291 / sbt#2953
@eed3si9n eed3si9n self-assigned this Feb 10, 2017
eed3si9n added a commit to eed3si9n/sbt that referenced this issue Feb 10, 2017
Previously, when the dependency resolver (Ivy) encountered a Maven version range such as `[1.3.0,)`
it would go out to the Internet to find the latest version.
This would result to a surprising behavior where the eventual version keeps changing over time
*even when there's a version of the library that satisfies the range condition*.

This changes to some Maven version ranges would be replaced with its lower bound
so that when a satisfactory version is found in the dependency graph it will be used.
You can disable this behavior using the JVM flag `-Dsbt.modversionrange=false`.

Fixes sbt#2954
Ref sbt#2291 / sbt#2953
eed3si9n added a commit to eed3si9n/sbt that referenced this issue Feb 10, 2017
Previously, when the dependency resolver (Ivy) encountered a Maven version range such as `[1.3.0,)`
it would go out to the Internet to find the latest version.
This would result to a surprising behavior where the eventual version keeps changing over time
*even when there's a version of the library that satisfies the range condition*.

This changes to some Maven version ranges would be replaced with its lower bound
so that when a satisfactory version is found in the dependency graph it will be used.
You can disable this behavior using the JVM flag `-Dsbt.modversionrange=false`.

Fixes sbt#2954
Ref sbt#2291 / sbt#2953
@dwijnand
Copy link
Member

I've been trying to wrap my head around what the correct behaviour should be, and it's hugely puzzling.

Generally, the java ecosystem both resolves and publishes maven artefacts using maven from/to maven repositories. While, generally, the scala ecosystem (ignoring sbt plugins, that use ivy repositories) both resolves and publishes maven artefacts using sbt/ivy (sbt's fork of ivy) from/to maven repositories.

So, using ivy, how should we reconcile different version ranges? Which semantics should we use? Ivy's? Maven's?

What even are Maven's semantics?

On http://maven.apache.org/components/enforcer/enforcer-rules/versionRanges.html it says about the version range: 1.0:

The default Maven meaning for 1.0 is everything (,) but with 1.0 recommended.

while on https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution it says:

"Soft" requirement on 1.0 (just a recommendation - helps select the correct version if it matches all ranges)

And what happens when you have multiple version ranges? On https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution it says:

Default strategy: Of the overlapping ranges, the highest soft requirement is the version to be used. If there are no soft requirements inside the prescribed ranges, the most recent version is used. If that does not fit the described ranges, then the most recent version number in the prescribed ranges is used. If the ranges exclude all versions, an error occurs.

while on https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN402 it says:

When Maven encounters multiple matches for a version reference, it uses the highest matching version. Generally, version references should be only as specific as required so that Maven is free to choose a new version of dependencies where appropriate, but knows when a specific version must be used. This enables Maven to choose the most appropriate version in cases where a dependency is specified at different points in the transitive dependency graph, with different versions. When a conflict like this occurs, Maven chooses the highest version from all references.

And that's just Maven. I haven't even begun to understand Ivy, where in http://ant.apache.org/ivy/history/latest-milestone/ivyfile/dependency.html it speaks about how there isn't just one "version" field (which they call "revision" typically) but of there being both a "rev" and a "revConstraint" field, and then:

.. the information of the original version constraint is not lost, but rather put in the revConstraint attribute


Clearly there is a lot going on and I'm personally not convinced (yet) that it's surprising that 1.5.0-beta.2, that it's "expected" to pick 1.4.7, or that the correct course of action is to simply "translate it to the lower bound 1.3.0". This needs further study, in my opinion.

@eed3si9n
Copy link
Member Author

eed3si9n commented Mar 5, 2017

So, using ivy, how should we reconcile different version ranges? Which semantics should we use? Ivy's? Maven's?

We use the combination of both. Ivy is used as the implementation but it tries to emulate Maven semantics via the force=true, and along the history of sbt library management we have been trying harder to get the emulation closer to the Maven semantics more and more.

In my opinion, the version range is a clear case that Ivy's implementation is broken. We can view dependency graph resolution to be a variant of constraint satisfaction problem where libraryDependency and the dependency among the libraries are the input. (In reality it's a more relaxed version of that because of version conflict) In this view, it's self evident that version [1.3.0,) means "version 1.3.0 or whatever version above it within the graph", NOT "find the the latest version from the Internet."

Also from the practical point of view, go-to-Internet behavior is detrimental to the core tenet of Repeatable Build. Say the author Alice publishes version 1 in 2015, and later someone tries to build the build, and here we assume zero SNAPSHOTs. The current behavior of go-to-Internet could still make the build impossible to reproduce if in 2017 the dependent library bumps up, or worse breaks bincompat. Using the lower bound will still satisfy the original constraint, and still do the right thing.

As per timing, I'm very much convinced that we have to do this pre-1.0.

@DavidPerezIngeniero
Copy link

What about Coursier?
https://github.com/alexarchambault/coursier

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

No branches or pull requests

3 participants