Skip to content

Commit 4c865c5

Browse files
committed
Add sbt incremental compilation support
To test this with sbt, see https://github.com/lampepfl/dotty/wiki/Using-Dotty-with-sbt The following flags are added: - -Yforce-sbt-phases: Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging. - -Ydump-sbt-inc: For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases. This commit introduces two new phases which do not transform trees: - `ExtractDependencies` which extracts the dependency information of the current compilation unit and sends it to sbt via callbacks - `ExtractAPI` which creates a representation of the API of the current compilation unit and sends it to sbt via callbacks Briefly, when a file changes sbt will recompile it, if its API has changed (determined by what `ExtractAPI` sent) then sbt will determine which reverse-dependencies (determined by what `ExtractDependencies` sent) of the API have to be recompiled depending on what changed. See http://www.scala-sbt.org/0.13/docs/Understanding-Recompilation.html for more information on 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: - ExtractDependencies runs right after Frontend (so that we don't lose dependency informations because of the simplifications done by PostTyper), but ExtractAPI runs right after PostTyper (so that SuperAccessors are part of the API). - `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 in `ExtractDependenciesCollector`. TODO: Try to run these phases in parallel with the rest of the compiler pipeline since they're independent (except for the sbt callbacks in `GenBCode`) ?
1 parent bcdddd9 commit 4c865c5

File tree

13 files changed

+1009
-11
lines changed

13 files changed

+1009
-11
lines changed

AUTHORS.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The majority of the dotty codebase is new code, with the exception of the compon
4242
> The utilities package is a mix of new and adapted components. The files in [scala/scala](https://github.com/scala/scala) were originally authored by many people,
4343
> including Paul Phillips, Martin Odersky, Sean McDirmid, and others.
4444
45-
`dotty.tools.io`
45+
`dotty.tools.io`
4646

4747
> The I/O support library was adapted from current Scala compiler. Original authors were Paul Phillips and others.
4848
@@ -52,3 +52,12 @@ The majority of the dotty codebase is new code, with the exception of the compon
5252
> [scala/scala](https://github.com/scala/scala). It has been reworked to fit
5353
> the needs of dotty. Original authors include: Adrian Moors, Lukas Rytz,
5454
> Grzegorz Kossakowski, Paul Phillips
55+
56+
`dotty.tools.dotc.sbt`
57+
58+
> The sbt compiler phases are based on
59+
> https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
60+
> which attempts to integrate the sbt phases into scalac and is itself based on
61+
> the [compiler bridge in sbt 0.13](https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt),
62+
> but has been heavily adapted and refactored.
63+
> Original authors were Mark Harrah, Grzegorz Kossakowski, Martin Duhemm, Adriaan Moors and others.

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"

project/Build.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ object DottyBuild extends Build {
8888
libraryDependencies ++= Seq("org.scala-lang.modules" %% "scala-xml" % "1.0.1",
8989
"org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test",
9090
"com.novocode" % "junit-interface" % "0.11" % "test",
91-
"jline" % "jline" % "2.12"),
91+
"jline" % "jline" % "2.12",
92+
"org.scala-sbt" % "interface" % sbtVersion.value),
9293

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

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class Compiler {
4242
def phases: List[List[Phase]] =
4343
List(
4444
List(new FrontEnd), // Compiler frontend: scanner, parser, namer, typer
45+
List(new sbt.ExtractDependencies), // Sends information on classes' dependencies to sbt via callbacks
4546
List(new PostTyper), // Additional checks and cleanups after type checking
47+
List(new sbt.ExtractAPI), // Sends a representation of the API of classes to sbt via callbacks
4648
List(new Pickler), // Generate TASTY info
4749
List(new FirstTransform, // Some transformations to put trees into a canonical form
4850
new CheckReentrant), // Internal use only: Check that compiled program has no data races involving global vars

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 YforceSbtPhases = BooleanSetting("-Yforce-sbt-phases", "Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging.")
164+
val YdumpSbtInc = BooleanSetting("-Ydump-sbt-inc", "For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases.")
163165
def stop = YstopAfter
164166

165167
/** Area-specific debug output.

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

Lines changed: 8 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,12 @@ object Contexts {
8485
_compilerCallback = callback
8586
def compilerCallback: CompilerCallback = _compilerCallback
8687

88+
/** The sbt callback implementation if we are run from sbt, null otherwise */
89+
private[this] var _sbtCallback: AnalysisCallback = _
90+
protected def sbtCallback_=(callback: AnalysisCallback) =
91+
_sbtCallback = callback
92+
def sbtCallback: AnalysisCallback = _sbtCallback
93+
8794
/** The current context */
8895
private[this] var _period: Period = _
8996
protected def period_=(period: Period) = {
@@ -426,6 +433,7 @@ object Contexts {
426433
def setPeriod(period: Period): this.type = { this.period = period; this }
427434
def setMode(mode: Mode): this.type = { this.mode = mode; this }
428435
def setCompilerCallback(callback: CompilerCallback): this.type = { this.compilerCallback = callback; this }
436+
def setSbtCallback(callback: AnalysisCallback): this.type = { this.sbtCallback = callback; this }
429437
def setTyperState(typerState: TyperState): this.type = { this.typerState = typerState; this }
430438
def setReporter(reporter: Reporter): this.type = setTyperState(typerState.withReporter(reporter))
431439
def setNewTyperState: this.type = setTyperState(typerState.fresh(isCommittable = true))

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2484,10 +2484,14 @@ object Types {
24842484

24852485
abstract class ParamType extends BoundType {
24862486
def paramNum: Int
2487+
def paramName: Name
24872488
}
24882489

24892490
abstract case class MethodParam(binder: MethodType, paramNum: Int) extends ParamType with SingletonType {
24902491
type BT = MethodType
2492+
2493+
def paramName = binder.paramNames(paramNum)
2494+
24912495
override def underlying(implicit ctx: Context): Type = binder.paramTypes(paramNum)
24922496
def copyBoundType(bt: BT) = new MethodParamImpl(bt, paramNum)
24932497

@@ -2500,7 +2504,7 @@ object Types {
25002504
false
25012505
}
25022506

2503-
override def toString = s"MethodParam(${binder.paramNames(paramNum)})"
2507+
override def toString = s"MethodParam($paramName)"
25042508
}
25052509

25062510
class MethodParamImpl(binder: MethodType, paramNum: Int) extends MethodParam(binder, paramNum)
@@ -2530,9 +2534,11 @@ object Types {
25302534
case _ => false
25312535
}
25322536

2537+
def paramName = binder.paramNames(paramNum)
2538+
25332539
override def underlying(implicit ctx: Context): Type = binder.paramBounds(paramNum)
25342540
// no customized hashCode/equals needed because cycle is broken in PolyType
2535-
override def toString = s"PolyParam(${binder.paramNames(paramNum)})"
2541+
override def toString = s"PolyParam($paramName)"
25362542

25372543
override def computeHash = doHash(paramNum, binder.identityHash)
25382544

@@ -2784,9 +2790,9 @@ object Types {
27842790
if ((prefix eq cls.owner.thisType) || !cls.owner.isClass || ctx.erasedTypes) tp
27852791
else tp.substThis(cls.owner.asClass, prefix)
27862792

2787-
private var typeRefCache: Type = null
2793+
private var typeRefCache: TypeRef = null
27882794

2789-
def typeRef(implicit ctx: Context): Type = {
2795+
def typeRef(implicit ctx: Context): TypeRef = {
27902796
def clsDenot = if (prefix eq cls.owner.thisType) cls.denot else cls.denot.copySymDenotation(info = this)
27912797
if (typeRefCache == null)
27922798
typeRefCache =
@@ -2795,7 +2801,7 @@ object Types {
27952801
typeRefCache
27962802
}
27972803

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

28002806
// cached because baseType needs parents
28012807
private var parentsCache: List[TypeRef] = null

0 commit comments

Comments
 (0)