sbt / sbt-eclipse

Plugin for sbt to create Eclipse project definitions
Apache License 2.0
716 stars 168 forks source link

`withSources` only works for library deps #336

Open godenji opened 7 years ago

godenji commented 7 years ago

When I eclipse generate my projects none of the plugins in project/plugins.sbt have corresponding <classpathentry sourcepath="..."> entries in the project's generated .classpath file. For library dependencies, no problem, sourcepath entries are generated correctly.

Running coursier the target plugins and their sources are downloaded so the sources are available on the file system.

// example /project/plugins.sbt "com.typesafe.play" % "sbt-plugin" % "2.6.0-M4" withSources

sbt 0.13.15 scala 2.12.2 sbteclipse 5.1.0

godenji commented 7 years ago

From an sbt session in a test project:

> reload plugins > eclipse

Downloads plugin sources and correctly generates .classpath file, but in project/, not the project root.

Beyond manually attaching plugin sources in Eclipse is there a way to do this via sbt-eclipse?

benmccann commented 7 years ago

Why do you need the plugin or it's sources on the project classpath?

godenji commented 7 years ago

@benmccann so I can browse the plugin sources in Eclipse?

In sbt when I define libraryDependencies += "my-dep" % ... % withSources() in my build the eclipse command generates 2 files for the IDE, .classpath and .project. In the .classpath file are <classpathentry sourcepath="..."> entries for the library. I can then click a library class or method within Eclipse and be taken directly to the source.

This does not happen, for some reason, with dependencies defined in project/plugins.sbt. There are several plugins I'd like to have sources be available for in Eclipse.

Maybe I'm not explaining this properly. Basically, somehow, I'd like the eclipse command to do the equivalent of "Attach Source" manually in the IDE for project/plugins.sbt deps.

godenji commented 7 years ago

Worked around with EclipseTransformerFactory by appending sourcepath to <classpath ...> in a custom rewrite handler.

Does the trick but would be nice if sbteclipse did this out of the box when plugins defined in project/plugins.sbt have withSources appended to dep line.

mkurz commented 7 years ago

@godenji Can you share the workaround? Thanks!

godenji commented 7 years ago

@mkurz sure, here's a stripped down version (have more rewrites to deal with in my implementation). Hacky, but better than manually attaching sources in Eclipse, no thanks ;-)

Add this to your settings once you have the below in place: EclipseKeys.classpathTransformerFactories := Seq(addSourcesManaged)


import com.typesafe.sbteclipse.core.EclipsePlugin.EclipseTransformerFactory
import com.typesafe.sbteclipse.core._
import scala.xml.transform.RewriteRule
import scala.xml.{Attribute, Text, Null, Node, Elem}
import scalaz.Scalaz._

def findPath(node: Node, search: String) =
  node.attributes.asAttrMap.get("path").
  filter(_.contains(search))

sealed trait ClasspathAttribs
object ClasspathAttribs {
  case object SOURCEPATH extends ClasspathAttribs
  case object OUTPUT extends ClasspathAttribs
}
import ClasspathAttribs._

def collectRewrites(node: Node): Set[(ClasspathAttribs, String)] = 
  Set(
    findPath(node, "/com/typesafe/play").map((SOURCEPATH, _)),
    findPath(node, "/org/scala-js").map((SOURCEPATH, _))
  ).flatten

lazy val addSourcesManaged =
  new EclipseTransformerFactory[RewriteRule] {
    override def createTransformer(ref: ProjectRef, state: State): Validation[RewriteRule] = {
      settingValidation(crossTarget in ref, state).map{ ct =>
        new RewriteRule {
          override def transform(node: Node): Seq[Node] = {
             val attribs = collectRewrites(node)
             node match {
               case el if el.label == "classpathentry" && attribs.exists(_._1 == SOURCEPATH) =>
                 val elem = Elem(
                   el.prefix, "classpathentry", el.attributes, el.scope, true
                 )
                 attribs.headOption.map(_._2).map { str =>
                   elem % Attribute(
                     None, "sourcepath", Text(str.replace(".jar", "-sources.jar")), Null
                   )    
                 } getOrElse(el)
             }
           }
         }
       }
     }
   }
godenji commented 7 years ago

@mkurz you'll need this as well

def structure(state: State): BuildStructure = Project.extract(state).structure
def settingValidation[A](key: SettingKey[A], state: State): Validation[A] =
  key.get(structure(state).data) match {
    case Some(a) => a.success
    case None => "Undefined setting '%s'!".format(key.key).failureNel
  }
mkurz commented 7 years ago

@godenji Thanks, but I am getting:

build.sbt:16: error: not found: object ClasspathAttribs
import ClasspathAttribs._
       ^
build.sbt:18: error: not found: type ClasspathAttribs
def collectRewrites(node: Node): Set[(ClasspathAttribs, String)] = 
                                      ^
build.sbt:20: error: not found: value SOURCEPATH
    findPath(node, "/com/typesafe/play").map((SOURCEPATH, _)),
                                              ^
build.sbt:21: error: not found: value SOURCEPATH
    findPath(node, "/org/scala-js").map((SOURCEPATH, _))
                                         ^
sbt.compiler.EvalException: Type error in expression
[error] sbt.compiler.EvalException: Type error in expression
godenji commented 7 years ago

Create a Transformers.scala file in your project/

trait Transformers {
  ... all the code
}
object Transformers extends Transformers

then in your build.sbt: import Transformers._

benmccann commented 7 years ago

Would you mind also pasting here a full example of what is added to the .classpath file by this code for one SBT plugin?

godenji commented 7 years ago

@benmccann

In the case of Play framework the plugin is split up into several different jars. Here's the original classpath entry for one of them, play-server:

<classpathentry kind="lib" path="/path/to/.coursier/cache/v1/https/repo1.maven.org/maven2/com/typesafe/play/play-server_2.12/2.6.0-M4/play-server_2.12-2.6.0-M4.jar"/>

and now with sourcepath appended:

<classpathentry sourcepath="/path/to/.coursier/cache/v1/https/repo1.maven.org/maven2/com/typesafe/play/play-server_2.12/2.6.0-M4/play-server_2.12-2.6.0-M4-sources.jar" kind="lib" path="/path/to/.coursier/cache/v1/https/repo1.maven.org/maven2/com/typesafe/play/play-server_2.12/2.6.0-M4/play-server_2.12-2.6.0-M4.jar"/>