Skip to content

Instantly share code, notes, and snippets.

@durban
Last active December 6, 2017 12:15
Show Gist options
  • Save durban/621cf5becd38b3dcbf3d3a0d4464b46d to your computer and use it in GitHub Desktop.
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)
/*
* 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