Eclipse + Typelevel compiler: put it under ~/.sbt/1.0/plugins/ and use the 'exlipse' command (requires sbteclipse 5.2.4)
December 6, 2017 ยท View on GitHub
/*
- Copyright 2016-2017 Daniel Urban
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License. */
import sbt._ import sbt.Keys._
import scala.xml.{ Attribute, Elem, MetaData, Node, NodeSeq, Null, Text, NamespaceBinding } import scala.xml.transform.RewriteRule
import com.typesafe.sbteclipse.core.EclipsePlugin.EclipseTransformerFactory import com.typesafe.sbteclipse.core.EclipsePlugin.EclipseKeys._
import scalaz.{ Validation, NonEmptyList }
/**
- This is a terrible hack to allow sbteclipse to cope
- with slightly nonstandard projects.
- It works around the following issues:
-
- no "-Ypartial-unification" in the presentation compiler
- (this is worked around by including the si2712fix-plugin
- in the generated Eclipse projects);
-
- no "-Xstrict-patmat-analysis" in the presentation compiler
- (this is simply removed from the options);
-
- "-Ymacro-expand:normal" is added to the options,
- because the presentation compiler defaults to "discard";
-
- compiler plugins which depend on a patch-level version of
- the Scala compiler (e.g., macro paradise) don't work if
- scalaVersion is not exactly the same as the presentation
- compiler version (this is worked around by replacing scalaVersion);
-
- duplicated Scala container classpath entries (probably due
- to the use of the Typelevel compiler; this is worked
- around by filtering these).
- All of the setting modifications are only performed to generate
- the Eclipse projects, and then they are reset to their original
- values.
- Usage:
-
- put this file under ~/.sbt/1.0/plugins/
-
- use the exlipse command in sbt to generate project files
-
- import the projects into Scala IDE 4.7.1 RC1. */ object EclipseHack extends AutoPlugin {
private val saveKey = AttributeKeySeq[Setting[_]]
override def requires = com.typesafe.sbteclipse.plugin.EclipsePlugin
override def trigger = allRequirements
override def projectSettings: Seq[Setting[_]] = Seq( classpathTransformerFactories += HackyFactory, commands += exlipseCommand )
private lazy val exlipseCommand: Command = Command.command("exlipse") { st => val modified: State = modifyOptions(st) modified.log.info("Running sbteclipse ...") val generated: State = modified.copy(remainingCommands = Exec("eclipse with-source=true", Some(Exec.newExecId), None) +: modified.remainingCommands) val resetted: State = resetOptions(generated) resetted }
private def modifyOptions(state: State): State = { state.log.info("Remembering current settings ...") val savedState = state.put(saveKey, Project.extract(state).session.rawAppend) savedState.log.info("Modifying scalacOptions ...") val extracted = Project.extract(savedState) val structure = extracted.structure val newSettings = structure.allProjectRefs.flatMap { ref => val scope = Scope(Select(ref), Zero, Zero, Zero) Seq( // this is only in TLS: scalacOptions in scope -= "-Xstrict-patmat-analysis", scalaOrganization in scope := "org.scala-lang", // for some compiler plugins: scalaVersion in scope := { val sv = (scalaVersion in scope).value if (sv.startsWith("2.12")) "2.12.3" else if (sv.startsWith("2.11")) "2.11.8" else sv }, // this is only in 2.11.11 and 2.12 and TLS: scalacOptions in scope --= (if (!(scalaVersion in scope).value.startsWith("2.12")) { Seq("-Ypartial-unification") } else { Seq() }), libraryDependencies in scope ++= (if (!(scalaVersion in scope).value.startsWith("2.12")) { Seq(compilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.2.0" cross CrossVersion.patch)) } else { Seq() }), // changed in 2.12.3: scalacOptions in scope := { val opts = (scalacOptions in scope).value opts.filter { case "-opt:l:inline" => state.log.info("Leaving out -opt:l:inline for Eclipse") false case s if s.startsWith("-opt-inline-from:") => state.log.info("Leaving out -opt-inline-from:... for Eclipse") false case x => true } }, // the PC defaults to "discard", but this causes less invalid errors: scalacOptions in scope += "-Ymacro-expand:normal" ) } val newSession = extracted.session.copy(rawAppend = extracted.session.rawAppend ++ newSettings) BuiltinCommands.reapply(newSession, structure, savedState) }
private def resetOptions(state: State): State = { state.get(saveKey) match { case Some(rawAppend) => state.log.info("Resetting modified settings ...") val extracted = Project.extract(state) val resettedSession = extracted.session.copy(rawAppend = rawAppend) BuiltinCommands.reapply(resettedSession, extracted.structure, state).remove(saveKey) case None => state.log.warn("No modified settings found!") state } }
private object HackyFactory extends EclipseTransformerFactory[RewriteRule] {
private val CpEntry = "classpathentry"
private val Cpath = "classpath"
private val ScalaContainer = "org.scala-ide.sdt.launching.SCALA_CONTAINER"
private val ScalaCompiler = "org.scala-ide.sdt.launching.SCALA_COMPILER_CONTAINER"
override def createTransformer(ref: ProjectRef, state: State): Validation[NonEmptyList[String], RewriteRule] = {
val rr: RewriteRule = new RewriteRule {
override def transform(node: Node): Seq[Node] = node match {
case Elem(pf, CpEntry, attrs, scope, children @ _*) if isScalaContainer(attrs) || isScalaCompiler(attrs) =>
NodeSeq.Empty
case Elem(pf, Cpath, attrs, scope, children @ _*) =>
val newChildren = children :+
container(ScalaContainer, pf, scope) :+
container(ScalaCompiler, pf, scope)
Elem(pf, Cpath, attrs, scope, newChildren: _*)
case x =>
x
}
private def container(name: String, pf: String, scope: NamespaceBinding): Elem =
Elem(pf, CpEntry, Attribute("kind", Text("con"), Attribute("path", Text(name), Null)), scope)
private def isScalaContainer(metaData: MetaData): Boolean =
isContainer(metaData, ScalaContainer)
private def isScalaCompiler(metaData: MetaData): Boolean =
isContainer(metaData, ScalaCompiler)
private def isContainer(metaData: MetaData, typ: String): Boolean = {
(metaData("kind") == Text("con")) &&
(Option(metaData("path").text) map (_ == typ) getOrElse false)
}
}
Validation.success(rr)
}
} }