Last active
December 6, 2017 12:15
-
-
Save durban/621cf5becd38b3dcbf3d3a0d4464b46d to your computer and use it in GitHub Desktop.
Eclipse + Typelevel compiler: put it under ~/.sbt/1.0/plugins/ and use the 'exlipse' command (requires sbteclipse 5.2.4)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 = AttributeKey[Seq[Setting[_]]]("exlipseSaveKey") | |
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) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment