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

Add coursier implementation of dependency resolution API #190

Closed
wants to merge 57 commits into from

Conversation

leonardehrenfried
Copy link
Contributor

@leonardehrenfried leonardehrenfried commented Dec 7, 2017

This implements a Coursier implementation of the dependency management API.

This exploratory implementation is somewhat beta as some functionality is missing. I am opening this pull request in order to gauge interest in the wider community in this approach and whether it's worth me spending time polishing this further.

With only this PR, sbt itself will not include a way of using this. A separate PR on sbt/sbt is needed for this.

General approach

I've tried to use as much as possible from the coursier plugin itself. In particular the classes FromSbt and ToSbt are straight up copies from their repository.

Not implemented yet/problems

  • Classifiers
  • Plugins are successfully resolved but don't show up in the sbt command prompt. How does plugin auto-discovery work?
  • Coursier does not implement cached resolution
  • Coursier does not implement timing reports in UpdateReport

I am hoping that @dwijnand, @eed3si9n and @alexarchambault can provide feedback on how to implement the missing features.

Questions

  • Should the local Ivy cache still be included as a default resolver?
  • What is the meaning of UpdateReport ModuleDescriptor.extraInputHash?

As I said above I would love some feedback about whether this has a chance of being merged or if there are any blockers that need to be removed first.

References

@eed3si9n eed3si9n added the ready label Dec 7, 2017
@alexarchambault
Copy link
Contributor

@leonardehrenfried I can't review it in detail right now, but that looks really cool.

About the ivy2 cache, if you meant ~/.ivy2/local, yes it should be kept, as coursier doesn't handle publishing for now. If you meant ~/.ivy2/cache, it shouldn't, but that one shouldn't show up, does it?

Is the code from FromSbt and ToSbt simple copy / paste? Maybe it could be spin up to a separate coursier-sbt module (with mima checks) in the sources of coursier.

@leonardehrenfried
Copy link
Contributor Author

leonardehrenfried commented Dec 7, 2017

About the ivy2 cache, if you meant ~/.ivy2/local, yes it should be kept, as coursier doesn't handle publishing for now. If you meant ~/.ivy2/cache, it shouldn't, but that one shouldn't show up, does it?

I was thinking about both. Right now the Ivy dep resolution includes them by default. I would also think that local makes a lot of sense but not sure if cache should be kept for backwards-compatibility. I added it manually but happy to remove it again.

Is the code from FromSbt and ToSbt simple copy / paste? Maybe it could be spin up to a separate coursier-sbt module (with mima checks) in the sources of coursier.

Yes, it's intentionally a dumb c&p so sharing code is made easier. I would love a reusable module that I can depend on!

@eed3si9n
Copy link
Member

@leonardehrenfried Thanks for taking on this task!

What's the meaning of extraInputHash?

extraInputHash is part of ModuleDescriptor, not UpdateReport.
Since update is needed for classpaths, and classpaths are needed for compile, etc update is called all the time, and the result is normally cached. This means that we need to be able to detect when we need to actually perform the update task (resolving and downloading) when things change on the ModuleDescriptor. Since ModuleDescriptor is an open trait, it won't know about fields its subclasses might introduce. This is where extraInputHash is used.

For Ivy it looks like this:

    def extraInputHash: Long = {
      import AltLibraryManagementCodec._
      Hasher.hash(owner.configuration) match {
        case Success(keyHash) => keyHash.toLong
        case Failure(_)       => 0L
      }
    }

@eed3si9n
Copy link
Member

If we are talking about bringing Coursier into to the librarymanagement code base (as opposed to living in the plugin space as alternative lm) one of my concerns about Coursier is its dependencies (see coursier/coursier#175, coursier/coursier#25, com-lihaoyi/Ammonite#644, etc).

@alexarchambault Do you have rough timeline for 1.0 and 1.1?

@leonardehrenfried
Copy link
Contributor Author

Coursier 1.0.0 has been released today, so I will update the PR accordingly. Handily, this includes a module that contains all the sbt <-> coursier conversion, so the diff will become smaller.

@eed3si9n I understand your reservation about pulling in Scalaz. In this comment @alexarchambault says that he will be working on removing the scalaz dependency. Do you feel it's condition of this PR being merged or is it simply a strong preference?

@eed3si9n
Copy link
Member

Since Alex has been publicly on board with reducing the dependencies, I'd much rather wait until it's done.

@alexarchambault
Copy link
Contributor

@eed3si9n Yep, it's something I'd like to tackle quickly.

@jvican
Copy link
Member

jvican commented Jan 13, 2018

Is there any progress on this awesome effort?

@leonardehrenfried
Copy link
Contributor Author

leonardehrenfried commented Jan 14, 2018

There is some progress, but there are a couple of issues that block me from progressing further:

So in summary, there are a few things that need to fall into place for this to happen. If people want to help out they could work on removing Scalaz from coursier.

*Excellent argument for sbt-release-early.

@leonardehrenfried leonardehrenfried changed the base branch from 1.x to 1.1.x January 14, 2018 20:26
@leonardehrenfried
Copy link
Contributor Author

For future reference, here is the compilation/linking error that I'm getting with coursier 1.0.0:

[error] /home/travis/build/sbt/librarymanagement/coursier/src/main/scala/sbt/librarymanagement/coursier/CoursierDependencyResolution.scala:68:39: Symbol 'type <none>.Import.Resolver' is missing from the classpath.
[error] This symbol is required by 'value coursier.FromSbt.resolver'.
[error] Make sure that type Resolver is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
[error] A full rebuild may help if 'FromSbt.class' was compiled against an incompatible version of <none>.Import.
[error]       reorderedResolvers.flatMap(r => FromSbt.repository(r, ivyConfiguration, log, authentication)) ++ Seq(
[error]                                       ^
[error] /home/travis/build/sbt/librarymanagement/coursier/src/main/scala/sbt/librarymanagement/coursier/CoursierDependencyResolution.scala:145:9: Symbol 'type <none>.Import.Logger' is missing from the classpath.
[error] This symbol is required by 'value coursier.ToSbt.log'.
[error] Make sure that type Logger is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
[error] A full rebuild may help if 'ToSbt.class' was compiled against an incompatible version of <none>.Import.
[error]         ToSbt.updateReport(
[error]         ^  

As I said upthread I think it's a problem with coursier-shared depending on sbt which messes up the classpath, but I'm having a hard time figuring out what exactly this module depends on. I will take another look once 1.0.1 is released.

@leonardehrenfried
Copy link
Contributor Author

After having taken a look here https://repo1.maven.org/maven2/io/get-coursier/sbt-shared_2.12_1.0/1.0.0/sbt-shared-1.0.0.pom

I see that coursier-shared definitely depends on "org.scala-sbt" %% "sbt". That should probably be reduced to just "org.scala-sbt" %% "sbt-librarymanagement".

I'll raise ticket with coursier.

@alexarchambault
Copy link
Contributor

alexarchambault commented Mar 29, 2018

Wouldn't adding coursier as a git submodule be ok, rather than copy-pasting FromSbt / ToSbt? (manually adding FromSbt.scala / ToSbt.scala via ~unmanagedSources)

@leonardehrenfried
Copy link
Contributor Author

I've seen that sometimes during local development, and usually clean fixes it. Do you see that locally too?

@eed3si9n I haven't actually run the scripted tests locally but will try.

Should this issue be even possible on CI with a clean target folder?

Wouldn't adding coursier as a git submodule be ok, rather than copy-pasting FromSbt / ToSbt? (manually adding FromSbt.scala / ToSbt.scala via ~unmanagedSources)

@alexarchambault I would be open to this but I'm not sure what the policy for this is. Maybe Eugene or Dale can chime in.

@eed3si9n
Copy link
Member

sbt's license is https://github.com/sbt/sbt/blob/1.x/LICENSE + https://www.lightbend.com/contribute/cla. This grants copyright to Lightbend, so we probably should get the written ok from @alexarchambault on this. My general preference is that we host librarymanagement code in sbt/librarymanagement repo. I'd also be happy to give repo rights to both of you assuming this PR goes through.

@eed3si9n
Copy link
Member

Should this issue be even possible on CI with a clean target folder?

I am not sure exactly. One thing I noticed is that InstrumentScripted is not in any package, and there's something odd that happens if you don't put an object in a package, so could you try changing that? https://github.com/sbt/sbt/blob/v1.1.2/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala#L136

@leonardehrenfried
Copy link
Contributor Author

@eed3si9n I tried putting InstrumentScripted into a package but that didn't work. I also tried it locally and unfortunately it didn't work either.

Can you take a look at the diff of my branch of sbt and check if there is anything in there that could cause the failure: sbt/sbt@1.x...leonardehrenfried:coursier

Here is the latest run of the scripted test of that branch: https://travis-ci.org/leonardehrenfried/sbt/jobs/360044405

@eed3si9n
Copy link
Member

Something is not hooked up correctly either in your lm or sbt. I added the following line to debug your classpath:

diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala
index dd9de5bc1..7672a65eb 100644
--- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala
+++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala
@@ -322,6 +322,7 @@ final class ScriptedTests(
           IO.copyDirectory(originalDir, tempTestDir)

           val runTest = () => {
+            IO.write(tempTestDir / "project" / "p.sbt", """logLevel := Level.Debug""")

and I am not seeing sbt JARs passed into Zinc:

[info] [debug] [zinc] Running cached compiler 72e02473 for Scala compiler version 2.12.4
[info] [debug] [zinc] The Scala compiler is invoked with:
[info] [debug] 	-deprecation
[info] [debug] 	-bootclasspath
[info] [debug] 	/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/classes:/var/folders/hg/2602nfrs2958vnshglyl3srw0000gn/T/sbt_1e8dd449/global/boot/scala-2.12.4/lib/scala-library.jar
[info] [debug] 	-classpath
[info] [debug] 	/private/var/folders/hg/2602nfrs2958vnshglyl3srw0000gn/T/sbt_1e8dd449/project/target/scala-2.12/sbt-1.0/classes
[info] [error] /private/var/folders/hg/2602nfrs2958vnshglyl3srw0000gn/T/sbt_1e8dd449/project/InstrumentScripted.scala:2:8: not found: object sbt
[info] [error] import sbt._, Keys._
[info] [error]        ^

compare that with 1.x branch:

[info] [debug] Getting org.scala-sbt:compiler-bridge_2.12:1.1.3:compile for Scala 2.12.4
[info] [debug] [zinc] Running cached compiler 5cc1fdcb for Scala compiler version 2.12.4
[info] [debug] [zinc] The Scala compiler is invoked with:
[info] [debug] 	-deprecation
[info] [debug] 	-bootclasspath
[info] [debug] 	/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/classes:/var/folders/hg/2602nfrs2958vnshglyl3srw0000gn/T/sbt_12ef7ae4/global/boot/scala-2.12.4/lib/scala-library.jar
[info] [debug] 	-classpath
[info] [debug] 	/private/var/folders/hg/2602nfrs2958vnshglyl3srw0000gn/T/sbt_12ef7ae4/project/target/scala-2.12/sbt-1.0/classes:/Users/eed3si9n/.ivy2/local/org.scala-sbt/sbt/1.2.0-SNAPSHOT/jars/sbt.jar:
/Users/eed3si9n/.ivy2/local/org.scala-sbt/main_2.12/1.2.0-SNAPSHOT/jars/main_2.12.jar:
/Users/eed3si9n/.ivy2/local/org.scala-sbt/logic_2.12/1.2.0-SNAPSHOT/jars/logic_2.12.jar:
/Users/eed3si9n/.ivy2/local/org.scala-sbt/collections_2.12/1.2.0-SNAPSHOT/jars/collections_2.12.jar:
/Users/eed3si9n/.ivy2/cache/com.eed3si9n/sjson-new-scalajson_2.12/jars/sjson-new-scalajson_2.12-0.8.2.jar:
/Users/eed3si9n/.ivy2/cache/com.eed3si9n/sjson-new-core_2.12/jars/sjson-new-core_2.12-0.8.2.jar:
/Users/eed3si9n/.ivy2/cache/com.eed3si9n/shaded-scalajson_2.12/jars/shaded-scalajson_2.12-1.0.0-M4.jar:
/Users/eed3si9n/.ivy2/cache/org.spire-math/jawn-parser_2.12/jars/jawn-parser_2.12-0.10.4.jar:
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/util-position_2.12/jars/util-position_2.12-1.1.3.jar:
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/util-relation_2.12/jars/util-relation_2.12-1.1.3.jar:
/Users/eed3si9n/.ivy2/local/org.scala-sbt/actions_2.12/1.2.0-SNAPSHOT/jars/actions_2.12.jar:
/Users/eed3si9n/.ivy2/local/org.scala-sbt/completion_2.12/1.2.0-SNAPSHOT/jars/completion_2.12.jar:
/Users/eed3si9n/.ivy2/cache/jline/jline/jars/jline-2.14.6.jar:
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/io_2.12/jars/io_2.12-1.1.4.jar:
/Users/eed3si9n/.ivy2/cache/net.java.dev.jna/jna/jars/jna-4.5.0.jar:
/Users/eed3si9n/.ivy2/cache/net.java.dev.jna/jna-platform/jars/jna-platform-4.5.0.jar:
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/util-control_2.12/jars/util-control_2.12-1.1.3.jar:
/Users/eed3si9n/.ivy2/local/org.scala-sbt/run_2.12/1.2.0-SNAPSHOT/jars/run_2.12.jar:
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/util-logging_2.12/jars/util-logging_2.12-1.1.3.jar:
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/util-interface/jars/util-interface-1.1.3.jar:
/Users/eed3si9n/.ivy2/cache/org.apache.logging.log4j/log4j-api/jars/log4j-api-2.8.1.jar:
/Users/eed3si9n/.ivy2/cache/org.apache.logging.log4j/log4j-core/jars/log4j-core-2.8.1.jar:
....
/Users/eed3si9n/.ivy2/cache/org.scala-sbt/compiler-bridge_2.12/jars/compiler-bridge_2.12-1.1.3.jar

@jvican
Copy link
Member

jvican commented Mar 30, 2018

sbt's license is sbt/sbt:LICENSE@1.x + lightbend.com/contribute/cla. This grants copyright to Lightbend, so we probably should get the written ok from @alexarchambault on this. My general preference is that we host librarymanagement code in sbt/librarymanagement repo. I'd also be happy to give repo rights to both of you assuming this PR goes through.

Why should @alexarchambault grant any copyright to Lightbend? sbt is currently using a fork of ivy (sbt/ivy) and happily using ivy's Apache license. There should be no problem that coursier is used as is.

@eed3si9n
Copy link
Member

@jvican

Why should @alexarchambault grant any copyright to Lightbend? sbt is currently using a fork of ivy (sbt/ivy) and happily using ivy's Apache license. There should be no problem that coursier is used as is.

There's a difference in the way software is licensed, binary is used, and how the source is controlled.

If we use Ivy as an example:

  1. as you pointed out, we literally forked it, so we control the patch and release cycle.
  2. sbt/librarymanagement uses the forked Ivy as binary dependency
  3. lm-ivy code is licensed under BSD-3 with Lightbend copyright.

For Coursier, similarly, @alexarchambault should rightfully retain the copyright etc of the Coursier itself. (At this point, I don't see the need to fork Coursier). But if any source code becomes part of this pull request, then we request BSD-3 with Lightbend CLA to be applied for the glue code. Since Leonard is not the original author of FromSbt.scala and ToSbt.scala, I thought we would eventually require written consent of the CLA.

@jvican
Copy link
Member

jvican commented Mar 31, 2018

But if any source code becomes part of this pull request, then we request BSD-3 with Lightbend CLA to be applied for the glue code.

Sure, just wanted to emphasize that the glue code should not be checked in the coursier repository, and therefore there's no need to ask Alex to re-license coursier. My opinion as an outsider is that the big benefit of using coursier, aside from the fact that it's way better than ivy, is that the sbt team doesn't need to maintain it -- it only needs to take care of the code that integrates with it, which is already an advantage over maintaining sbt/ivy! Having coursier versioned and licensed as an independent tool makes things much easier 😄.

@leonardehrenfried
Copy link
Contributor Author

We might be able to sidestep the entire issue by me trying a little harder to include coursier/sbt-shared. It is an sbt plugin of the build but also required at runtime, which I guess is confusing to the dependency resolution.
It is not listed as a dependency of lmCoursier when you run publishLocal so that's why the sbt/sbt build cannot find it. Maybe it works if I manually readd it not as a plugin but as a dependency like this:

"io.get-coursier" % "sbt-shared_2.12_1.0" % "1.1.0-M1"

The same would then also be needed for sbt-compat.

@leonardehrenfried
Copy link
Contributor Author

Ok, I just tried it out and it doesn't work because that would resolve the following:

https://repo1.maven.org/maven2/io/get-coursier/sbt-shared_2.12_1.0/1.1.0-M1/sbt-shared_2.12_1.0-1.1.0-M1.pom

When in actual fact the correct URL is:

https://repo1.maven.org/maven2/io/get-coursier/sbt-shared_2.12_1.0/1.1.0-M1/sbt-shared-1.1.0-M1.pom

@leonardehrenfried
Copy link
Contributor Author

Maybe I need to add it as an sbt-plugin of lmCoursier and of sbt itself. But I'm not sure what is worse: Copying two classes or trying to get sbt to transitively include an sbt plugin.

@leonardehrenfried
Copy link
Contributor Author

leonardehrenfried commented Apr 1, 2018

I figured out why the scripted tests break. The dependency "org.scala-sbt" % "sbt" % "1.1.0" % "provided" isn't resolved transitively by coursier because of the provided scope.

@eed3si9n @alexarchambault I'm not sure what the correct behaviour is here. Should provided dependencies be transitively resolved?

@eed3si9n
Copy link
Member

eed3si9n commented Apr 1, 2018

See https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

This scope is only available on the compilation and test classpath, and is not transitive.

@eed3si9n
Copy link
Member

eed3si9n commented Apr 2, 2018

https://github.com/sbt/sbt/blob/v1.1.2/main/src/main/scala/sbt/Defaults.scala#L2068-L2072

      val sbtdeps =
        (sbtDependency in pluginCrossBuild).value.withConfigurations(Some(Provided.name))
      val pluginAdjust =
        if (isPlugin) sbtdeps +: base
        else base

The code above adds sbt as a provided dependency to the metabuild's root project directly. So I am not sure why you'd need to resolve it transitively.

@leonardehrenfried
Copy link
Contributor Author

Then I'm officially out of ideas why this doesn't work.

If those jars are manually added to the classpath, how come that the compiler cannot find sbt.Keys?

@alexarchambault
Copy link
Contributor

(I'm fine with signing the CLA if that's needed or to clarify things.)

@dwijnand
Copy link
Member

dwijnand commented Jun 1, 2018

@leonardehrenfried would you be able to join our sbt meeting? Eugene is not available next week Wednesday, so perhaps Wednesday 13 June? We'd love to get a chance to chat with you (and @alexarchambault too if you'd like to join!) about this PR.

Alternatively or in addition: are you going to be at Scala Days NYC?

@leonardehrenfried
Copy link
Contributor Author

Hi, sorry for not following up on this PR.

I'd gladly join a an sbt meeting either at the coming week or the week after that.

Sadly, I was too snowed under at work to attend scala days in Berlin even though it happened quite close to where I live. :|

@leonardehrenfried
Copy link
Contributor Author

So, to answer your question: no, unfortunately I won't be at Scala Days NYC.

@eed3si9n eed3si9n added backlog and removed ready labels Aug 21, 2018
@eed3si9n eed3si9n changed the base branch from 1.x to develop August 30, 2018 15:21
@andreaTP
Copy link
Contributor

this can be closed now.

@eed3si9n
Copy link
Member

Closing this in favor of #270

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

Successfully merging this pull request may close these issues.

None yet

6 participants