Skip to content

Commit 36f22d6

Browse files
committed
Add sbt incremental compilation support via a special phase
To test this with sbt, see https://github.com/lampepfl/dotty/wiki/Using-Dotty-with-sbt To test this by hand use `-Ydump-sbt-api` This phase does not transform any tree. See http://www.scala-sbt.org/0.13/docs/Understanding-Recompilation.html for an overview of how sbt incremental compilation works. This phase was originally based on https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt which attempts to integrate the sbt phases into scalac (and is itself based on https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt), but it has been heavily refactored and adapted to Dotty. The main functional differences are: - This phase runs right after the typer, before PostTyper which gets rid of important information for incremental compilation (it simplifies trees that have a constant type and trees that represent types). - `ExtractAPI` only extract types as they are defined and never "as seen from" some some specific prefix, see its documentation for more details. - `ExtractDependenciesTraverser` and `ExtractUsedNames` have been fused into one tree traversal. TODO: Try to run this phase in parallel with the rest of the compiler pipeline since they're independent (except for the sbt callbacks in `GenBCode`) ?
1 parent 12d0515 commit 36f22d6

File tree

17 files changed

+967
-15
lines changed

17 files changed

+967
-15
lines changed

bin/dotc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ SCALA_BINARY_VERSION=2.11
2323
SCALA_COMPILER_VERSION=$(getLastStringOnLineWith "scala-compiler")
2424
DOTTY_VERSION=$(getLastStringOnLineWith "version in")
2525
JLINE_VERSION=$(getLastStringOnLineWith "jline")
26+
SBT_VERSION=$(grep "sbt.version=" "$DOTTY_ROOT/project/build.properties" | sed 's/sbt.version=//')
2627
bootcp=true
2728
bootstrapped=false
2829
default_java_opts="-Xmx768m -Xms768m"
@@ -100,13 +101,19 @@ then
100101
JLINE_JAR=$HOME/.ivy2/cache/jline/jline/jars/jline-$JLINE_VERSION.jar
101102
fi
102103

103-
if [ ! -f "$SCALA_LIBRARY_JAR" -o ! -f "$SCALA_REFLECT_JAR" -o ! -f "$SCALA_COMPILER_JAR" -o ! -f "$JLINE_JAR" ]
104+
if [ "$SBT_INTERFACE_JAR" == "" ]
105+
then
106+
SBT_INTERFACE_JAR=$HOME/.ivy2/cache/org.scala-sbt/interface/jars/interface-$SBT_VERSION.jar
107+
fi
108+
109+
if [ ! -f "$SCALA_LIBRARY_JAR" -o ! -f "$SCALA_REFLECT_JAR" -o ! -f "$SCALA_COMPILER_JAR" -o ! -f "$JLINE_JAR" -o ! -f "$SBT_INTERFACE_JAR" ]
104110
then
105111
echo To use this script please set
106112
echo SCALA_LIBRARY_JAR to point to scala-library-$SCALA_VERSION.jar "(currently $SCALA_LIBRARY_JAR)"
107113
echo SCALA_REFLECT_JAR to point to scala-reflect-$SCALA_VERSION.jar "(currently $SCALA_REFLECT_JAR)"
108114
echo SCALA_COMPILER_JAR to point to scala-compiler-$SCALA_VERSION.jar with bcode patches "(currently $SCALA_COMPILER_JAR)"
109115
echo JLINE_JAR to point to jline-$JLINE_VERSION.jar "(currently $JLINE_JAR)"
116+
echo SBT_INTERFACE_JAR to point to interface-$SBT_VERSION.jar "(currently $SBT_INTERFACE_JAR)"
110117
fi
111118

112119
ifdebug () {
@@ -196,9 +203,9 @@ trap onExit INT
196203
classpathArgs () {
197204
if [[ "true" == $bootstrapped ]]; then
198205
checkjar $DOTTY_JAR "test:runMain dotc.build" src
199-
toolchain="$DOTTY_JAR:$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR"
206+
toolchain="$DOTTY_JAR:$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR:$SBT_INTERFACE_JAR"
200207
else
201-
toolchain="$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR"
208+
toolchain="$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR:$SBT_INTERFACE_JAR"
202209
fi
203210
bcpJars="$INTERFACES_JAR:$MAIN_JAR"
204211
cpJars="$INTERFACES_JAR:$MAIN_JAR:$TEST_JAR"

interfaces/src/main/java/dotty/tools/dotc/interfaces/CompilerCallback.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package dotty.tools.dotc.interfaces;
22

3+
import java.io.File;
4+
35
/** Set of callbacks called in response to events during the compilation process.
46
*
57
* You should implement this interface if you want to react to one or more of
@@ -25,4 +27,12 @@ public interface CompilerCallback {
2527
* Example: ./src/library/scala/collection/Seq.scala
2628
*/
2729
default void onSourceCompiled(SourceFile source) {};
30+
31+
default void sourceDependency(File dependsOn, File source, xsbti.DependencyContext context) {};
32+
33+
default void binaryDependency(File binary, String name, File source, xsbti.DependencyContext context) {};
34+
35+
default void api(File sourceFile, xsbti.api.SourceAPI source) {};
36+
37+
default void usedName(File sourceFile, String names) {};
2838
}

project/Build.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ object DottyBuild extends Build {
2222

2323
override def settings: Seq[Setting[_]] = {
2424
super.settings ++ Seq(
25+
publishArtifact in packageDoc := false,
2526
scalaVersion in Global := "2.11.5",
2627
version in Global := "0.1-SNAPSHOT",
2728
organization in Global := "org.scala-lang",
@@ -47,7 +48,8 @@ object DottyBuild extends Build {
4748
// Do not depend on the Scala library
4849
autoScalaLibrary := false,
4950
//Remove javac invalid options in Compile doc
50-
javacOptions in (Compile, doc) --= Seq("-Xlint:unchecked", "-Xlint:deprecation")
51+
javacOptions in (Compile, doc) --= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
52+
libraryDependencies += "org.scala-sbt" % "interface" % sbtVersion.value
5153
)
5254

5355
lazy val dotty = project.in(file(".")).
@@ -84,7 +86,8 @@ object DottyBuild extends Build {
8486
libraryDependencies ++= Seq("org.scala-lang.modules" %% "scala-xml" % "1.0.1",
8587
"org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test",
8688
"com.novocode" % "junit-interface" % "0.11" % "test",
87-
"jline" % "jline" % "2.12"),
89+
"jline" % "jline" % "2.12",
90+
"org.scala-sbt" % "interface" % sbtVersion.value),
8891

8992
// enable improved incremental compilation algorithm
9093
incOptions := incOptions.value.withNameHashing(true),

src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context
569569
def isConstructor: Boolean = toDenot(sym).isConstructor
570570
def isAnonymousFunction: Boolean = toDenot(sym).isAnonymousFunction
571571
def isMethod: Boolean = sym is Flags.Method
572-
def isPublic: Boolean = sym.flags.is(Flags.EmptyFlags, Flags.Private | Flags.Protected)
572+
def isPublic: Boolean = toDenot(sym).isPublic
573573
def isSynthetic: Boolean = sym is Flags.Synthetic
574574
def isPackageClass: Boolean = sym is Flags.PackageClass
575575
def isModuleClass: Boolean = sym is Flags.ModuleClass

src/dotty/tools/backend/jvm/GenBCode.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter
389389
val className = jclassName.replace('/', '.')
390390
if (ctx.compilerCallback != null)
391391
ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(outFile), className)
392+
if (ctx.sbtCallback != null)
393+
ctx.sbtCallback.generatedClass(sourceFile.jfile.orElse(null), outFile.file, className)
392394
}
393395
catch {
394396
case e: FileConflictException =>

src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Compiler {
4242
def phases: List[List[Phase]] =
4343
List(
4444
List(new FrontEnd), // Compiler frontend: scanner, parser, namer, typer
45+
List(new sbt.SbtPhase), // Gather information for sbt incremental compilation
4546
List(new PostTyper), // Additional checks and cleanups after type checking
4647
List(new Pickler), // Generate TASTY info
4748
List(new FirstTransform, // Some transformations to put trees into a canonical form

src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ object desugar {
958958
* Example: Given
959959
*
960960
* class C
961-
* type T1 extends C { type T <: A }
961+
* type T1 = C { type T <: A }
962962
*
963963
* the refined type
964964
*

src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,10 @@ object Trees {
559559
type ThisTree[-T >: Untyped] = SingletonTypeTree[T]
560560
}
561561

562-
/** qualifier # name */
562+
/** qualifier # name
563+
* In Scala, this always refers to a type, but in a Java
564+
* compilation unit this might refer to a term.
565+
*/
563566
case class SelectFromTypeTree[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name)
564567
extends RefTree[T] {
565568
type ThisTree[-T >: Untyped] = SelectFromTypeTree[T]

src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ class ScalaSettings extends Settings.SettingGroup {
160160
val YtestPickler = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler")
161161
val YcheckReentrant = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.")
162162
val YkeepComments = BooleanSetting("-Ykeep-comments", "Keep comments when scanning source files.")
163+
val YforceSbtPhase = BooleanSetting("-Yforce-sbt-phase", "Run the sbt incremental compiler phase even if the compiler is launched outside of sbt, for debugging.")
164+
val YdumpSbtApi = BooleanSetting("-Ydump-sbt-api", "For every compiled foo.scala, output a foo.api from the sbt API representation, implies -Yforce-sbt-phase.")
163165
def stop = YstopAfter
164166

165167
/** Area-specific debug output.

src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import printing._
2929
import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform}
3030
import language.implicitConversions
3131
import DenotTransformers.DenotTransformer
32+
import xsbti.AnalysisCallback
3233

3334
object Contexts {
3435

@@ -84,6 +85,22 @@ object Contexts {
8485
_compilerCallback = callback
8586
def compilerCallback: CompilerCallback = _compilerCallback
8687

88+
private[this] var _sbtCallback: AnalysisCallback = new AnalysisCallback {
89+
def api(x$1: java.io.File,x$2: xsbti.api.SourceAPI): Unit = {}
90+
def binaryDependency(x$1: java.io.File,x$2: String,x$3: java.io.File,x$4: Boolean): Unit = {}
91+
def generatedClass(x$1: java.io.File,x$2: java.io.File,x$3: String): Unit = {}
92+
def problem(x$1: String,x$2: xsbti.Position,x$3: String,x$4: xsbti.Severity,x$5: Boolean): Unit = {}
93+
def sourceDependency(x$1: java.io.File,x$2: java.io.File,x$3: xsbti.DependencyContext): Unit = {}
94+
def sourceDependency(x$1: java.io.File,x$2: java.io.File,x$3: Boolean): Unit = {}
95+
def usedName(x$1: java.io.File,x$2: String): Unit = {}
96+
def binaryDependency(x$1: java.io.File,x$2: String,x$3: java.io.File,x$4: xsbti.DependencyContext): Unit = {}
97+
def nameHashing(): Boolean = true
98+
99+
} //_
100+
protected def sbtCallback_=(callback: AnalysisCallback) =
101+
_sbtCallback = callback
102+
def sbtCallback: AnalysisCallback = _sbtCallback
103+
87104
/** The current context */
88105
private[this] var _period: Period = _
89106
protected def period_=(period: Period) = {
@@ -426,6 +443,7 @@ object Contexts {
426443
def setPeriod(period: Period): this.type = { this.period = period; this }
427444
def setMode(mode: Mode): this.type = { this.mode = mode; this }
428445
def setCompilerCallback(callback: CompilerCallback): this.type = { this.compilerCallback = callback; this }
446+
def setSbtCallback(callback: AnalysisCallback): this.type = { this.sbtCallback = callback; this }
429447
def setTyperState(typerState: TyperState): this.type = { this.typerState = typerState; this }
430448
def setReporter(reporter: Reporter): this.type = setTyperState(typerState.withReporter(reporter))
431449
def setNewTyperState: this.type = setTyperState(typerState.fresh(isCommittable = true))

src/dotty/tools/dotc/core/Phases.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ object Phases {
140140
* if squashing is enabled, phases in same subgroup will be squashed to single phase.
141141
*/
142142
def usePhases(phasess: List[Phase], squash: Boolean = true) = {
143-
144143
val flatPhases = collection.mutable.ListBuffer[Phase]()
145144

146145
phasess.foreach(p => p match {

src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,8 @@ object SymDenotations {
10601060
else defn.RootClass
10611061
}
10621062

1063+
def isPublic(implicit ctx: Context): Boolean = !this.is(Protected | Private) && !privateWithin.exists
1064+
10631065
/** The primary constructor of a class or trait, NoSymbol if not applicable. */
10641066
def primaryConstructor(implicit ctx: Context): Symbol = NoSymbol
10651067

src/dotty/tools/dotc/core/Types.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2478,10 +2478,14 @@ object Types {
24782478

24792479
abstract class ParamType extends BoundType {
24802480
def paramNum: Int
2481+
def paramName: Name
24812482
}
24822483

24832484
abstract case class MethodParam(binder: MethodType, paramNum: Int) extends ParamType with SingletonType {
24842485
type BT = MethodType
2486+
2487+
def paramName = binder.paramNames(paramNum)
2488+
24852489
override def underlying(implicit ctx: Context): Type = binder.paramTypes(paramNum)
24862490
def copyBoundType(bt: BT) = new MethodParamImpl(bt, paramNum)
24872491

@@ -2494,7 +2498,7 @@ object Types {
24942498
false
24952499
}
24962500

2497-
override def toString = s"MethodParam(${binder.paramNames(paramNum)})"
2501+
override def toString = s"MethodParam($paramName)"
24982502
}
24992503

25002504
class MethodParamImpl(binder: MethodType, paramNum: Int) extends MethodParam(binder, paramNum)
@@ -2524,9 +2528,11 @@ object Types {
25242528
case _ => false
25252529
}
25262530

2531+
def paramName = binder.paramNames(paramNum)
2532+
25272533
override def underlying(implicit ctx: Context): Type = binder.paramBounds(paramNum)
25282534
// no customized hashCode/equals needed because cycle is broken in PolyType
2529-
override def toString = s"PolyParam(${binder.paramNames(paramNum)})"
2535+
override def toString = s"PolyParam($paramName)"
25302536

25312537
override def computeHash = doHash(paramNum, binder.identityHash)
25322538

@@ -2778,9 +2784,9 @@ object Types {
27782784
if ((prefix eq cls.owner.thisType) || !cls.owner.isClass || ctx.erasedTypes) tp
27792785
else tp.substThis(cls.owner.asClass, prefix)
27802786

2781-
private var typeRefCache: Type = null
2787+
private var typeRefCache: TypeRef = null
27822788

2783-
def typeRef(implicit ctx: Context): Type = {
2789+
def typeRef(implicit ctx: Context): TypeRef = {
27842790
def clsDenot = if (prefix eq cls.owner.thisType) cls.denot else cls.denot.copySymDenotation(info = this)
27852791
if (typeRefCache == null)
27862792
typeRefCache =
@@ -2789,7 +2795,7 @@ object Types {
27892795
typeRefCache
27902796
}
27912797

2792-
def symbolicTypeRef(implicit ctx: Context): Type = TypeRef(prefix, cls)
2798+
def symbolicTypeRef(implicit ctx: Context): TypeRef = TypeRef(prefix, cls)
27932799

27942800
// cached because baseType needs parents
27952801
private var parentsCache: List[TypeRef] = null
@@ -3402,6 +3408,12 @@ object Types {
34023408
}
34033409
}
34043410

3411+
abstract class TypeTraverser(implicit ctx: Context) extends TypeAccumulator[Unit] {
3412+
def traverse(tp: Type): Unit
3413+
def apply(x: Unit, tp: Type): Unit = traverse(tp)
3414+
protected def traverseChildren(tp: Type) = foldOver((), tp)
3415+
}
3416+
34053417
class ExistsAccumulator(p: Type => Boolean)(implicit ctx: Context) extends TypeAccumulator[Boolean] {
34063418
override def stopAtStatic = false
34073419
def apply(x: Boolean, tp: Type) = x || p(tp) || foldOver(x, tp)

0 commit comments

Comments
 (0)