Skip to content

Commit df57a1e

Browse files
authored
Merge pull request #1528 from rochala/undercompilation-local-dependency
[1.x] Local dependency invalidation improvement
2 parents 0bb578b + 95a88e4 commit df57a1e

File tree

20 files changed

+120
-13
lines changed

20 files changed

+120
-13
lines changed

internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala

+10-9
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ private[inc] abstract class IncrementalCommon(
9898
invalidatedSources,
9999
classfileManager,
100100
pruned,
101-
previous,
102101
classesToRecompile,
103102
profiler.registerCycle(
104103
invalidatedClasses,
@@ -149,11 +148,11 @@ private[inc] abstract class IncrementalCommon(
149148
invalidatedSources: Set[VirtualFile],
150149
classFileManager: XClassFileManager,
151150
pruned: Analysis,
152-
override val previousAnalysis: Analysis,
153151
classesToRecompile: Set[String],
154152
registerCycle: (Set[String], APIChanges, Set[String], Boolean) => Unit
155153
) extends IncrementalCallback(classFileManager) {
156154
override val isFullCompilation: Boolean = allSources.subsetOf(invalidatedSources)
155+
override val previousAnalysis: Analysis = previous
157156
override val previousAnalysisPruned: Analysis = pruned
158157

159158
override def mergeAndInvalidate(
@@ -165,10 +164,12 @@ private[inc] abstract class IncrementalCommon(
165164
partialAnalysis.copy(compilations = pruned.compilations ++ partialAnalysis.compilations)
166165
else pruned ++ partialAnalysis
167166

168-
// Represents classes detected as changed externally and internally (by a previous cycle)
167+
// Represents all classes that were compiled as a result of external and internal invalidation (by a previous cycle)
169168
// Maps the changed sources by the user to class names we can count as invalidated
170169
val getClasses = (a: Analysis) => initialChangedSources.flatMap(a.relations.classNames)
171-
val recompiledClasses = classesToRecompile ++ getClasses(previous) ++ getClasses(analysis)
170+
val recompiledClasses = classesToRecompile ++
171+
getClasses(previous) ++ getClasses(analysis) ++
172+
invalidatedSources.flatMap(previous.relations.classNames)
172173

173174
val newApiChanges =
174175
detectAPIChanges(recompiledClasses, previous.apis.internalAPI, analysis.apis.internalAPI)
@@ -499,11 +500,11 @@ private[inc] abstract class IncrementalCommon(
499500
Set.empty
500501
} else {
501502
if (invalidateTransitively) {
502-
val firstClassTransitiveInvalidation = includeTransitiveInitialInvalidations(
503-
initial,
504-
IncrementalCommon.transitiveDeps(initial, log)(dependsOnClass),
505-
dependsOnClass
506-
)
503+
// NOTE: As member reference relations do not include local relations, this invalidation will fully propagate
504+
// thus we can't rely solely on `firstClassTransitiveInvalidation`. Better bet is to try to find transitive
505+
// dependencies from result of `firstClassInvalidation`
506+
val firstClassTransitiveInvalidation =
507+
IncrementalCommon.transitiveDeps(firstClassInvalidation, log)(dependsOnClass)
507508
log.debug("Invalidate by brute force:\n\t" + firstClassTransitiveInvalidation)
508509
firstClassTransitiveInvalidation ++ secondClassInvalidation ++ thirdClassInvalidation ++ recompiledClasses
509510
} else {

internal/zinc-core/src/main/scala/sbt/internal/inc/Relations.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,15 @@ trait Relations {
184184
private[inc] def externalDependencies: ExternalDependencies
185185

186186
/**
187-
* The class dependency relation between classes introduced by member reference.
187+
* The class dependency relation between classes introduced
188+
* by member reference excluding excluding same-source references.
188189
*
189190
* NOTE: All inheritance dependencies are included in this relation because in order to
190191
* inherit from a member you have to refer to it. If you check documentation of `inheritance`
191192
* you'll see that there's small oddity related to traits being the first parent of a
192193
* class/trait that results in additional parents being introduced due to normalization.
193-
* This relation properly accounts for that so the invariant that `memberRef` is a superset
194-
* of `inheritance` is preserved.
194+
*
195+
* Because `inheritance` includes same-source references, `memberRef` is not a superset of `inheritance`
195196
*/
196197
private[inc] def memberRef: ClassDependencies
197198

internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,10 @@ case class ProjectStructure(
810810
import scala.collection.JavaConverters._
811811
val map = new java.util.HashMap[String, String]
812812
properties.asScala foreach { case (k: String, v: String) => map.put(k, v) }
813-
val externalHooks = new DefaultExternalHooks(Optional.empty[ExternalHooks.Lookup], Optional.empty[XClassFileManager])
813+
val externalHooks = new DefaultExternalHooks(
814+
Optional.empty[ExternalHooks.Lookup],
815+
Optional.empty[XClassFileManager]
816+
)
814817
.withInvalidationProfiler(profiler)
815818
val base = IncOptions
816819
.of()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait A {
2+
def buildNonemptyObjects(a: Int): Int = a
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Main extends App {
2+
val x: C = new C { }
3+
val z: Int = x.x
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
trait C extends B {
2+
def x = something
3+
}
4+
trait B extends A {
5+
def something = buildNonemptyObjects(5)
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
trait A {
2+
// change return type Int => String
3+
def buildNonemptyObjects(a: Int): String = ""
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait A {
2+
def buildNonemptyObjects(a: Int): Int = a
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
transitiveStep = 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
transitiveStep = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
##################### FAILURE ############################
2+
# clean build that compiles with transitiveStep = 1
3+
> compile
4+
5+
# incremental build that should fail with transitiveStep = 1
6+
$ copy-file changes/A.scala A.scala
7+
-> compile
8+
9+
##################### FAILURE ############################
10+
11+
# rerun the test with transitiveStep = 3
12+
> clean
13+
$ copy-file changes/OriginalA.scala A.scala
14+
$ copy-file changes/incOptions.properties incOptions.properties
15+
16+
# clean build that compiles with transitiveStep = 3
17+
> compile
18+
19+
# incremental build that should fail with transitiveStep = 3
20+
$ copy-file changes/A.scala A.scala
21+
-> compile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object A {
2+
def buildNonemptyObjects(a: Int): Int = a
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Main extends App {
2+
val z: Int = C.x
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object C {
2+
def x = B.something
3+
}
4+
5+
object B {
6+
def something = A.buildNonemptyObjects(5)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object A {
2+
// change return type Int => String
3+
def buildNonemptyObjects(a: Int): String = ""
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object A {
2+
def buildNonemptyObjects(a: Int): Int = a
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object A {
2+
// add default param
3+
def buildNonemptyObjects(a: Int, b: Int = 5): Int = a
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
transitiveStep = 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
transitiveStep = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
##################### SUCCESS ############################
2+
# clean build that compiles with transitiveStep = 1
3+
> compile
4+
5+
# incremental build that should work with transitiveStep = 1
6+
$ copy-file changes/WorkingA.scala A.scala
7+
> compile
8+
# > checkIterations 2 this is not yet working, but it should be 2
9+
10+
##################### FAILURE ############################
11+
12+
# clean build that compiles with transitiveStep = 1
13+
> clean
14+
$ copy-file changes/OriginalA.scala A.scala
15+
> compile
16+
17+
# incremental build that should fail with transitiveStep = 1
18+
$ copy-file changes/A.scala A.scala
19+
-> compile
20+
21+
##################### FAILURE ############################
22+
23+
# rerun the test with transitiveStep = 3
24+
> clean
25+
$ copy-file changes/OriginalA.scala A.scala
26+
$ copy-file changes/incOptions.properties incOptions.properties
27+
28+
# clean build that compiles with transitiveStep = 3
29+
> compile
30+
31+
# incremental build that should fail with transitiveStep = 3
32+
$ copy-file changes/A.scala A.scala
33+
-> compile

0 commit comments

Comments
 (0)