From 0de8345e33a40964edbb5420a0fd03a5a37e5bcb Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 13 Apr 2018 14:11:07 -0700 Subject: [PATCH] Remove watch loops When source generators write into the unmanaged source directory, bad things can happen. Continuous builds will loop indefinitely and compiling will fail because the generated sources get added to the source list twice, causing the incremental compiler to complain about compiling classes it has already seen. My two-pronged solution is to de-duplicate the sources task and to filter out managed source files in watch sources. The drawback to the latter is that it causes the source generation task to be executed twice per compile. --- main/src/main/scala/sbt/Defaults.scala | 15 +++++++++++++-- sbt/src/sbt-test/tests/watch-loop/build.sbt | 17 +++++++++++++++++ .../watch-loop/project/SourceWrapper.scala | 6 ++++++ .../tests/watch-loop/src/main/scala/Bar.scala | 1 + .../tests/watch-loop/src/main/scala/Foo.scala | 1 + sbt/src/sbt-test/tests/watch-loop/test | 1 + 6 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 sbt/src/sbt-test/tests/watch-loop/build.sbt create mode 100644 sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala create mode 100644 sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala create mode 100644 sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala create mode 100644 sbt/src/sbt-test/tests/watch-loop/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 82af2a9e4..847d95ee2 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -329,7 +329,18 @@ object Defaults extends BuildCommon { val baseDir = baseDirectory.value val bases = unmanagedSourceDirectories.value val include = (includeFilter in unmanagedSources).value - val exclude = (excludeFilter in unmanagedSources).value + val exclude = (excludeFilter in unmanagedSources).value match { + case e => + (managedSources in ThisScope).value match { + case l if l.nonEmpty => + e || new FileFilter { + private val files = l.toSet + override def accept(pathname: File): Boolean = files.contains(pathname) + override def toString = s"ManagedSourcesFilter($files)" + } + case _ => e + } + } val baseSources = if (sourcesInBase.value) Seq(new Source(baseDir, include, exclude, recursive = false)) else Nil @@ -341,7 +352,7 @@ object Defaults extends BuildCommon { sourceDirectories := Classpaths .concatSettings(unmanagedSourceDirectories, managedSourceDirectories) .value, - sources := Classpaths.concat(unmanagedSources, managedSources).value + sources := Classpaths.concatDistinct(unmanagedSources, managedSources).value ) lazy val resourceConfigPaths = Seq( resourceDirectory := sourceDirectory.value / "resources", diff --git a/sbt/src/sbt-test/tests/watch-loop/build.sbt b/sbt/src/sbt-test/tests/watch-loop/build.sbt new file mode 100644 index 000000000..effea2c79 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/build.sbt @@ -0,0 +1,17 @@ +import java.nio.file.Files + +lazy val watchLoopTest = taskKey[Unit]("Check that managed sources are filtered") + +sourceGenerators in Compile += Def.task { + val path = baseDirectory.value.toPath.resolve("src/main/scala/Foo.scala") + Files.write(path, "object Foo".getBytes).toFile :: Nil +} + +watchLoopTest := { + val watched = watchSources.value + val managedSource = (managedSources in Compile).value.head + assert(!SourceWrapper.accept(watched, managedSource)) + assert((sources in Compile).value.foldLeft((true, Set.empty[File])) { + case ((res, set), f) => (res && !set.contains(f), set + f) + }._1) +} diff --git a/sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala b/sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala new file mode 100644 index 000000000..ff351168a --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala @@ -0,0 +1,6 @@ +package sbt + +object SourceWrapper { + def accept(sources: Seq[sbt.internal.io.Source], file: File): Boolean = + sources.exists(_.accept(file.toPath)) +} diff --git a/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala new file mode 100644 index 000000000..3b43dece6 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala @@ -0,0 +1 @@ +object Bar diff --git a/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala new file mode 100644 index 000000000..d37c10456 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala @@ -0,0 +1 @@ +object Foo \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/watch-loop/test b/sbt/src/sbt-test/tests/watch-loop/test new file mode 100644 index 000000000..41d621982 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/test @@ -0,0 +1 @@ +> watchLoopTest