summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2017-11-16 03:40:58 -0800
committerLi Haoyi <haoyi.sg@gmail.com>2017-11-16 03:42:08 -0800
commit28dbe29a2ab566249642e81405a953f33507828a (patch)
treef70dae7ddd6b741b040d585d85a135c58b920a96
parentc2a3514c70ad357f73c0ed209966616e01f9d703 (diff)
downloadmill-28dbe29a2ab566249642e81405a953f33507828a.tar.gz
mill-28dbe29a2ab566249642e81405a953f33507828a.tar.bz2
mill-28dbe29a2ab566249642e81405a953f33507828a.zip
Vendor `com.lihaoyi:acyclic` codebase as a cross-building example, first non-working experiments in cross building working...
-rw-r--r--core/src/main/scala/mill/define/Cross.scala18
-rw-r--r--core/src/main/scala/mill/define/Task.scala2
-rw-r--r--core/src/test/scala/mill/CacherTests.scala2
-rw-r--r--core/src/test/scala/mill/CrossTests.scala47
-rw-r--r--core/src/test/scala/mill/EvaluationTests.scala12
-rw-r--r--core/src/test/scala/mill/JavaCompileJarTests.scala2
-rw-r--r--core/src/test/scala/mill/MacroErrorTests.scala6
-rw-r--r--core/src/test/scala/mill/TestGraphs.scala6
-rw-r--r--scalaplugin/src/main/scala/mill/scalaplugin/GenIdea.scala4
-rw-r--r--scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala (renamed from scalaplugin/src/main/scala/mill/scalaplugin/Module.scala)10
-rw-r--r--scalaplugin/src/test/resource/acyclic/build.sbt49
-rw-r--r--scalaplugin/src/test/resource/acyclic/build.sc116
-rw-r--r--scalaplugin/src/test/resource/acyclic/project/build.properties1
-rw-r--r--scalaplugin/src/test/resource/acyclic/project/build.sbt2
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/main/resources/scalac-plugin.xml4
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/package.scala23
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/DependencyExtraction.scala100
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/GraphAnalysis.scala103
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/Plugin.scala26
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/PluginPhase.scala180
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/A.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/B.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/C.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/D.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/E.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A1.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A2.scala4
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/package.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B1.scala3
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B2.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/package.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/A.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/B.scala3
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C1.scala3
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C2.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/package.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/A.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/B.scala3
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/C.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/A.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/B.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/A.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/B.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/A.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/B.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/A.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/B.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/A.scala4
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/B.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/C.scala3
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/D.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/E.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/java/SomeJava.java4
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A1.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A2.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/package.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B1.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B2.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A1.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A2.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/package.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A1.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A2.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B1.scala6
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B2.scala7
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/single/pkg/package.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/A.scala4
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/B.scala5
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/CycleTests.scala68
-rw-r--r--scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/TestUtils.scala92
70 files changed, 1112 insertions, 22 deletions
diff --git a/core/src/main/scala/mill/define/Cross.scala b/core/src/main/scala/mill/define/Cross.scala
new file mode 100644
index 00000000..c84e9b3d
--- /dev/null
+++ b/core/src/main/scala/mill/define/Cross.scala
@@ -0,0 +1,18 @@
+package mill.define
+
+
+case class Cross[+T](items: T*){
+ def flatMap[V](f: T => Cross[V]): Cross[(T, V)] = {
+ val flattened = for{
+ i <- items
+ k <- f(i).items
+ } yield (i, k)
+ Cross(flattened:_*)
+ }
+ def map[V](f: T => V): Cross[(T, V)] = {
+ Cross(items.map(i => i -> f(i)):_*)
+ }
+ def withFilter(f: T => Boolean) = {
+ Cross(items.filter(f):_*)
+ }
+}
diff --git a/core/src/main/scala/mill/define/Task.scala b/core/src/main/scala/mill/define/Task.scala
index ea197039..0853b06b 100644
--- a/core/src/main/scala/mill/define/Task.scala
+++ b/core/src/main/scala/mill/define/Task.scala
@@ -50,7 +50,7 @@ object Task extends Applicative.Applyer[Task, Task, Args]{
def source(path: ammonite.ops.Path) = new Source(path)
- trait Cacher extends mill.define.Cacher[Task, Target]{
+ trait Module extends mill.define.Cacher[Task, Target]{
def wrapCached[T](t: Task[T], enclosing: String): Target[T] = new TargetImpl(t, enclosing)
}
class Task0[T](t: T) extends Task[T]{
diff --git a/core/src/test/scala/mill/CacherTests.scala b/core/src/test/scala/mill/CacherTests.scala
index 5931691d..6e0087b1 100644
--- a/core/src/test/scala/mill/CacherTests.scala
+++ b/core/src/test/scala/mill/CacherTests.scala
@@ -9,7 +9,7 @@ import utest.framework.TestPath
object CacherTests extends TestSuite{
object Base extends Base
- trait Base extends Task.Cacher{
+ trait Base extends Task.Module{
def value = T{ 1 }
}
object Middle extends Middle
diff --git a/core/src/test/scala/mill/CrossTests.scala b/core/src/test/scala/mill/CrossTests.scala
new file mode 100644
index 00000000..ebf5d065
--- /dev/null
+++ b/core/src/test/scala/mill/CrossTests.scala
@@ -0,0 +1,47 @@
+package mill
+
+import mill.define.Cross
+import utest._
+
+object CrossTests extends TestSuite{
+
+ val tests = Tests{
+ def assertEquals[T](value: Cross[T], value1: Cross[T]) = {
+ assert(value == value1)
+ }
+ 'map - assertEquals(
+ for(a <- Cross(1, 2, 3)) yield a.toString,
+ Cross(1 -> "1", 2 -> "2", 3 -> "3")
+ )
+ 'flatMapFilter - assertEquals(
+ for{
+ a <- Cross(1, 2)
+ b <- Cross("A", "B")
+ if !(a == 2 && b == "B")
+ } yield b * a,
+ Cross(
+ (1 -> ("A" -> "A")),
+ (1 -> ("B" -> "B")),
+ (2 -> ("A" -> "AA"))
+ )
+ )
+ 'reuse - {
+ val matrix = for{
+ a <- Cross(1, 2)
+ b <- Cross("A", "B")
+ if !(a == 2 && b == "B")
+ } yield ()
+ assertEquals(
+ for((a, (b, _)) <- matrix)
+ yield b * a,
+ Cross(
+ (1 -> ("A" -> "A")),
+ (1 -> ("B" -> "B")),
+ (2 -> ("A" -> "AA"))
+ )
+ )
+ }
+
+
+ }
+}
diff --git a/core/src/test/scala/mill/EvaluationTests.scala b/core/src/test/scala/mill/EvaluationTests.scala
index 32d2b847..a5fd72a8 100644
--- a/core/src/test/scala/mill/EvaluationTests.scala
+++ b/core/src/test/scala/mill/EvaluationTests.scala
@@ -3,7 +3,7 @@ package mill
import mill.TestUtil.{Test, test}
import mill.define.{Target, Task}
-import mill.define.Task.Cacher
+import mill.define.Task.Module
import mill.discover.Discovered
import mill.eval.Evaluator
import mill.util.OSet
@@ -170,7 +170,7 @@ object EvaluationTests extends TestSuite{
// task1 -------- right
// _/
// change - task2
- object build extends Cacher{
+ object build extends Module{
val task1 = T.task{ 1 }
def left = T{ task1() }
val change = test()
@@ -203,7 +203,7 @@ object EvaluationTests extends TestSuite{
// _ left _
// / \
// task -------- right
- object build extends Cacher{
+ object build extends Module{
val task = T.task{ 1 }
def left = T{ task() }
def right = T{ task() + left() + 1 }
@@ -222,7 +222,7 @@ object EvaluationTests extends TestSuite{
// _ left
// /
// task -------- right
- object build extends Cacher{
+ object build extends Module{
val task = T.task{ 1 }
def left = T{ task() }
def right = T{ task() }
@@ -241,7 +241,7 @@ object EvaluationTests extends TestSuite{
// _ left _____________
// / \ \
// task1 -------- right ----- task2
- object build extends Cacher{
+ object build extends Module{
val task1 = T.task{ 1 }
def left = T{ task1() }
def right = T{ task1() + left() + 1 }
@@ -266,7 +266,7 @@ object EvaluationTests extends TestSuite{
// up middle -- down
// /
// right
- object build extends Cacher{
+ object build extends Module{
var leftCount = 0
var rightCount = 0
var middleCount = 0
diff --git a/core/src/test/scala/mill/JavaCompileJarTests.scala b/core/src/test/scala/mill/JavaCompileJarTests.scala
index 39d3a93a..e19073a4 100644
--- a/core/src/test/scala/mill/JavaCompileJarTests.scala
+++ b/core/src/test/scala/mill/JavaCompileJarTests.scala
@@ -30,7 +30,7 @@ object JavaCompileJarTests extends TestSuite{
mkdir(pwd / 'target / 'workspace / 'javac)
cp(javacSrcPath, javacDestPath)
- object Build extends Task.Cacher{
+ object Build extends Task.Module{
def sourceRootPath = javacDestPath / 'src
def resourceRootPath = javacDestPath / 'resources
diff --git a/core/src/test/scala/mill/MacroErrorTests.scala b/core/src/test/scala/mill/MacroErrorTests.scala
index 20bf805a..b9de5594 100644
--- a/core/src/test/scala/mill/MacroErrorTests.scala
+++ b/core/src/test/scala/mill/MacroErrorTests.scala
@@ -10,7 +10,7 @@ object MacroErrorTests extends TestSuite{
val expectedMsg =
"T{} members must be defs defined in a Cacher class/trait/object body"
- val err = compileError("object Foo extends mill.define.Task.Cacher{ val x = T{1} }")
+ val err = compileError("object Foo extends mill.define.Task.Module{ val x = T{1} }")
assert(err.msg == expectedMsg)
}
@@ -35,7 +35,7 @@ object MacroErrorTests extends TestSuite{
val expectedMsg =
"Target#apply() call cannot use `value n` defined within the T{...} block"
- val err = compileError("""new mill.define.Task.Cacher{
+ val err = compileError("""new mill.define.Task.Module{
def a = T{ 1 }
val arr = Array(a)
def b = {
@@ -51,7 +51,7 @@ object MacroErrorTests extends TestSuite{
val expectedMsg =
"Target#apply() call cannot use `value x` defined within the T{...} block"
- val err = compileError("""new mill.define.Task.Cacher{
+ val err = compileError("""new mill.define.Task.Module{
def a = T{ 1 }
val arr = Array(a)
def b = {
diff --git a/core/src/test/scala/mill/TestGraphs.scala b/core/src/test/scala/mill/TestGraphs.scala
index 46fad053..1e69fc0b 100644
--- a/core/src/test/scala/mill/TestGraphs.scala
+++ b/core/src/test/scala/mill/TestGraphs.scala
@@ -1,6 +1,6 @@
package mill
-import mill.define.Task.Cacher
+import mill.define.Task.Module
import mill.TestUtil.test
class TestGraphs(){
@@ -43,7 +43,7 @@ class TestGraphs(){
val down = test(test(up), test(up))
}
- object defCachedDiamond extends Cacher{
+ object defCachedDiamond extends Module{
def up = T{ test() }
def left = T{ test(up) }
def right = T{ test(up) }
@@ -51,7 +51,7 @@ class TestGraphs(){
}
- object borkedCachedDiamond2 extends Cacher {
+ object borkedCachedDiamond2 extends Module {
def up = test()
def left = test(up)
def right = test(up)
diff --git a/scalaplugin/src/main/scala/mill/scalaplugin/GenIdea.scala b/scalaplugin/src/main/scala/mill/scalaplugin/GenIdea.scala
index f55efa4d..a2f6a8b1 100644
--- a/scalaplugin/src/main/scala/mill/scalaplugin/GenIdea.scala
+++ b/scalaplugin/src/main/scala/mill/scalaplugin/GenIdea.scala
@@ -19,10 +19,10 @@ object GenIdea {
def xmlFileLayout[T: Discovered](obj: T): Seq[(RelPath, scala.xml.Node)] = {
val discovered = implicitly[Discovered[T]]
- def rec(x: Hierarchy[T]): Seq[(Seq[String], Module)] = {
+ def rec(x: Hierarchy[T]): Seq[(Seq[String], ScalaModule)] = {
val node = x.node(obj)
val self = node match{
- case m: Module => Seq(x.path -> m)
+ case m: ScalaModule => Seq(x.path -> m)
case _ => Nil
}
diff --git a/scalaplugin/src/main/scala/mill/scalaplugin/Module.scala b/scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala
index f18c49fa..f5c895bb 100644
--- a/scalaplugin/src/main/scala/mill/scalaplugin/Module.scala
+++ b/scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala
@@ -6,7 +6,7 @@ import java.io.File
import ammonite.ops._
import coursier.{Cache, Fetch, MavenRepository, Repository, Resolution}
import mill.define.Task
-import mill.define.Task.Cacher
+import mill.define.Task.Module
import mill.discover.{Discovered, Hierarchy}
import mill.eval.{Evaluator, PathRef}
import mill.modules.Jvm.{createJar, createAssembly}
@@ -19,7 +19,7 @@ import xsbti.compile.DependencyChanges
-object Module{
+object ScalaModule{
def compileScala(scalaVersion: String,
sources: PathRef,
compileClasspath: Seq[PathRef],
@@ -118,9 +118,9 @@ object Module{
Dep.Java("org.scala-lang", "scala-library", scalaVersion)
)
}
-import Module._
+import ScalaModule._
-trait Module extends Cacher{
+trait ScalaModule extends Module{
def scalaVersion: T[String]
def scalaBinaryVersion = T{ scalaVersion().split('.').dropRight(1).mkString(".") }
@@ -134,7 +134,7 @@ trait Module extends Cacher{
MavenRepository("https://repo1.maven.org/maven2")
)
- def projectDeps = Seq.empty[Module]
+ def projectDeps = Seq.empty[ScalaModule]
def depClasspath = T{ Seq.empty[PathRef] }
diff --git a/scalaplugin/src/test/resource/acyclic/build.sbt b/scalaplugin/src/test/resource/acyclic/build.sbt
new file mode 100644
index 00000000..3fd0f8e4
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/build.sbt
@@ -0,0 +1,49 @@
+
+organization := "com.lihaoyi"
+
+name := "acyclic"
+
+version := "0.1.7"
+
+scalaVersion := "2.11.8"
+
+crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0")
+
+resolvers += Resolver.sonatypeRepo("releases")
+
+libraryDependencies ++= Seq(
+ "com.lihaoyi" %% "utest" % "0.4.4" % "test",
+ "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided"
+)
+
+testFrameworks += new TestFramework("utest.runner.Framework")
+
+unmanagedSourceDirectories in Test <+= baseDirectory(_ / "src" / "test" / "resources")
+
+// Sonatype
+publishArtifact in Test := false
+
+publishTo <<= version { (v: String) =>
+ Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2")
+}
+
+pomExtra := (
+ <url>https://github.com/lihaoyi/acyclic</url>
+ <licenses>
+ <license>
+ <name>MIT license</name>
+ <url>http://www.opensource.org/licenses/mit-license.php</url>
+ </license>
+ </licenses>
+ <scm>
+ <url>git://github.com/lihaoyi/utest.git</url>
+ <connection>scm:git://github.com/lihaoyi/acyclic.git</connection>
+ </scm>
+ <developers>
+ <developer>
+ <id>lihaoyi</id>
+ <name>Li Haoyi</name>
+ <url>https://github.com/lihaoyi</url>
+ </developer>
+ </developers>
+ )
diff --git a/scalaplugin/src/test/resource/acyclic/build.sc b/scalaplugin/src/test/resource/acyclic/build.sc
new file mode 100644
index 00000000..39bbc77a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/build.sc
@@ -0,0 +1,116 @@
+
+
+val acyclic =
+ for(crossVersion <- Cross("2.10.6", "2.11.8", "2.12.0"))
+ yield new ScalaModule{ main =>
+ def organization = "com.lihaoyi"
+ def name = "acyclic"
+ def scalaVersion = crossVersion
+ def version = "0.1.7"
+
+ override def compileIvyDeps = Seq(
+ Dep.Java("org.scala-lang", "scala-compiler", scalaVersion())
+ )
+
+ object Tests extends Module{
+ override def projectDeps = Seq(main)
+
+ override def ivyDeps = Seq(
+ Dep("com.lihaoyi", "utest", "0.6.0")
+ )
+ def test() = T.command{
+ TestRunner.apply(
+ "mill.UTestFramework",
+ runDepClasspath().map(_.path) :+ compile().path,
+ Seq(compile().path)
+ )
+ }
+ }
+ }
+
+
+
+//
+//object acyclic extends crossModule(Seq("2.10.6", "2.11.8", "2.12.0"))(crossVersion =>
+// new ScalaModule{
+// def organization = "com.lihaoyi"
+// def name = "acyclic"
+// def scalaVersion = crossVersion
+// def version = "0.1.7"
+//
+// override def compileIvyDeps = Seq(
+// Dep.Java("org.scala-lang", "scala-compiler", scalaVersion())
+// )
+//
+// object Tests extends Module{
+// override def projectDeps = Seq(Acyclic(scalaVersion))
+//
+// override def ivyDeps = Seq(
+// Dep("com.lihaoyi", "utest", "0.6.0")
+// )
+// def test() = T.command{
+// TestRunner.apply(
+// "mill.UTestFramework",
+// runDepClasspath().map(_.path) :+ compile().path,
+// Seq(compile().path)
+// )
+// }
+// }
+// }
+//
+//)
+//
+
+// Seq("2.10.6", "2.11.8", "2.12.0")
+//case class Acyclic(scalaVersion: String = "2.11.8") extends CrossModule{
+// def organization = "com.lihaoyi"
+// def name = "acyclic"
+// def version = "0.1.7"
+//
+// override def compileIvyDeps = Seq(
+// Dep.Java("org.scala-lang", "scala-compiler", scalaVersion())
+// )
+//}
+//
+//case class AcyclicTests(scalaVersion: String = "2.11.8") extends CrossModule{
+// override def projectDeps = Seq(Acyclic(scalaVersion))
+//
+// override def ivyDeps = Seq(
+// Dep("com.lihaoyi", "utest", "0.6.0")
+// )
+// def test() = T.command{
+// TestRunner.apply(
+// "mill.UTestFramework",
+// runDepClasspath().map(_.path) :+ compile().path,
+// Seq(compile().path)
+// )
+// }
+//}
+
+//unmanagedSourceDirectories in Test <+= baseDirectory(_ / "src" / "test" / "resources")
+//
+//// Sonatype
+//publishTo <<= version { (v: String) =>
+// Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2")
+//}
+//
+//pomExtra := (
+// <url>https://github.com/lihaoyi/acyclic</url>
+// <licenses>
+// <license>
+// <name>MIT license</name>
+// <url>http://www.opensource.org/licenses/mit-license.php</url>
+// </license>
+// </licenses>
+// <scm>
+// <url>git://github.com/lihaoyi/utest.git</url>
+// <connection>scm:git://github.com/lihaoyi/acyclic.git</connection>
+// </scm>
+// <developers>
+// <developer>
+// <id>lihaoyi</id>
+// <name>Li Haoyi</name>
+// <url>https://github.com/lihaoyi</url>
+// </developer>
+// </developers>
+// )
diff --git a/scalaplugin/src/test/resource/acyclic/project/build.properties b/scalaplugin/src/test/resource/acyclic/project/build.properties
new file mode 100644
index 00000000..817bc38d
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.13.9
diff --git a/scalaplugin/src/test/resource/acyclic/project/build.sbt b/scalaplugin/src/test/resource/acyclic/project/build.sbt
new file mode 100644
index 00000000..7a1f37db
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/project/build.sbt
@@ -0,0 +1,2 @@
+
+addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") \ No newline at end of file
diff --git a/scalaplugin/src/test/resource/acyclic/src/main/resources/scalac-plugin.xml b/scalaplugin/src/test/resource/acyclic/src/main/resources/scalac-plugin.xml
new file mode 100644
index 00000000..7fd6e95b
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/main/resources/scalac-plugin.xml
@@ -0,0 +1,4 @@
+<plugin>
+ <name>acyclic</name>
+ <classname>acyclic.plugin.RuntimePlugin</classname>
+</plugin> \ No newline at end of file
diff --git a/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/package.scala b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/package.scala
new file mode 100644
index 00000000..0d656160
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/package.scala
@@ -0,0 +1,23 @@
+import scala.reflect.internal.annotations.compileTimeOnly
+package object acyclic {
+ /**
+ * Import this within a file to make Acyclic verify that the file does not
+ * have any circular dependencies with other files.
+ */
+ @compileTimeOnly("acyclic.file is just a marker and not a real value")
+ def file = ()
+
+ /**
+ *
+ */
+ @compileTimeOnly("acyclic.file is just a marker and not a real value")
+ def skipped = ()
+
+ /**
+ * Import this within a package object to make Acyclic verify that the entire
+ * package does not have any circular dependencies with other files or
+ * packages. Circular dependencies *within* the package are Ok.
+ */
+ @compileTimeOnly("acyclic.pkg is just a marker and not a real value")
+ def pkg = ()
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/DependencyExtraction.scala b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/DependencyExtraction.scala
new file mode 100644
index 00000000..46aacc2b
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/DependencyExtraction.scala
@@ -0,0 +1,100 @@
+//acyclic
+package acyclic.plugin
+import acyclic.file
+import scala.tools.nsc.Global
+object DependencyExtraction{
+ def apply(global: Global)(unit: global.CompilationUnit): Seq[(global.Symbol, global.Tree)] = {
+ import global._
+
+ class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser {
+ var collected: List[T] = Nil
+ def traverse(tpe: Type): Unit = {
+ if (pf.isDefinedAt(tpe))
+ collected = pf(tpe) :: collected
+ mapOver(tpe)
+ }
+ }
+
+ class ExtractDependenciesTraverser extends Traverser {
+ protected val depBuf = collection.mutable.ArrayBuffer.empty[(Symbol, Tree)]
+ protected def addDependency(sym: Symbol, tree: Tree): Unit = depBuf += ((sym, tree))
+ def dependencies: collection.immutable.Set[(Symbol, Tree)] = {
+ // convert to immutable set and remove NoSymbol if we have one
+ depBuf.toSet
+ }
+
+ }
+
+ class ExtractDependenciesByMemberRefTraverser extends ExtractDependenciesTraverser {
+ override def traverse(tree: Tree): Unit = {
+ tree match {
+ case i @ Import(expr, selectors) =>
+
+ selectors.foreach {
+ case ImportSelector(nme.WILDCARD, _, null, _) =>
+ // in case of wildcard import we do not rely on any particular name being defined
+ // on `expr`; all symbols that are being used will get caught through selections
+ case ImportSelector(name: Name, _, _, _) =>
+ def lookupImported(name: Name) = expr.symbol.info.member(name)
+ // importing a name means importing both a term and a type (if they exist)
+ addDependency(lookupImported(name.toTermName), tree)
+ addDependency(lookupImported(name.toTypeName), tree)
+ }
+ case select: Select =>
+ addDependency(select.symbol, tree)
+ /*
+ * Idents are used in number of situations:
+ * - to refer to local variable
+ * - to refer to a top-level package (other packages are nested selections)
+ * - to refer to a term defined in the same package as an enclosing class;
+ * this looks fishy, see this thread:
+ * https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion
+ */
+ case ident: Ident =>
+ addDependency(ident.symbol, tree)
+ case typeTree: TypeTree =>
+ val typeSymbolCollector = new CollectTypeTraverser({
+ case tpe if !tpe.typeSymbol.isPackage => tpe.typeSymbol
+ })
+ typeSymbolCollector.traverse(typeTree.tpe)
+ val deps = typeSymbolCollector.collected.toSet
+ deps.foreach(addDependency(_, tree))
+ case Template(parents, self, body) =>
+ traverseTrees(body)
+ case other => ()
+ }
+ super.traverse(tree)
+ }
+ }
+
+ def byMembers(): collection.immutable.Set[(Symbol, Tree)] = {
+ val traverser = new ExtractDependenciesByMemberRefTraverser
+ if (!unit.isJava)
+ traverser.traverse(unit.body)
+ traverser.dependencies
+ }
+
+
+ class ExtractDependenciesByInheritanceTraverser extends ExtractDependenciesTraverser {
+ override def traverse(tree: Tree): Unit = tree match {
+ case Template(parents, self, body) =>
+ // we are using typeSymbol and not typeSymbolDirect because we want
+ // type aliases to be expanded
+ val parentTypeSymbols = parents.map(parent => parent.tpe.typeSymbol).toSet
+ debuglog("Parent type symbols for " + tree.pos + ": " + parentTypeSymbols.map(_.fullName))
+ parentTypeSymbols.foreach(addDependency(_, tree))
+ traverseTrees(body)
+ case tree => super.traverse(tree)
+ }
+ }
+
+ def byInheritence(): collection.immutable.Set[(Symbol, Tree)] = {
+ val traverser = new ExtractDependenciesByInheritanceTraverser
+ if (!unit.isJava)
+ traverser.traverse(unit.body)
+ traverser.dependencies
+ }
+
+ (byMembers() | byInheritence()).toSeq
+ }
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/GraphAnalysis.scala b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/GraphAnalysis.scala
new file mode 100644
index 00000000..bf72ce39
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/GraphAnalysis.scala
@@ -0,0 +1,103 @@
+package acyclic.plugin
+import acyclic.file
+import scala.tools.nsc.Global
+import collection.mutable
+
+sealed trait Value{
+ def pkg: List[String]
+ def prettyPrint: String
+}
+object Value{
+ case class File(path: String, pkg: List[String] = Nil) extends Value{
+ def prettyPrint = s"file $path"
+ }
+ case class Pkg(pkg: List[String]) extends Value{
+ def prettyPrint = s"package ${pkg.mkString(".")}"
+ }
+ object Pkg{
+ def apply(s: String): Pkg = apply(s.split('.').toList)
+ }
+}
+
+trait GraphAnalysis{
+ val global: Global
+ import global._
+
+ case class Node[+T <: Value](value: T, dependencies: Map[Value, Seq[Tree]]){
+ override def toString = s"DepNode(\n $value, \n ${dependencies.keys}\n)"
+ }
+
+ type DepNode = Node[Value]
+ type FileNode = Node[Value.File]
+ type PkgNode = Node[Value.Pkg]
+
+ object DepNode{
+ /**
+ * Does a double Breadth-First-Search to find the shortest cycle starting
+ * from `from` within the DepNodes in `among`.
+ */
+ def smallestCycle(from: DepNode, among: Seq[DepNode]): Seq[DepNode] = {
+ val nodeMap = among.map(n => n.value -> n).toMap
+ val distances = mutable.Map(from -> 0)
+ val queue = mutable.Queue(from)
+ while(queue.nonEmpty){
+ val next = queue.dequeue()
+ val children = next.dependencies
+ .keys
+ .collect(nodeMap)
+ .filter(!distances.contains(_))
+
+ children.foreach(distances(_) = distances(next) + 1)
+ queue.enqueue(children.toSeq:_*)
+ }
+ var route = List(from)
+ while(route.length == 1 || route.head != from){
+ route ::= among.filter(x => x.dependencies.keySet.contains(route.head.value))
+ .minBy(distances)
+ }
+ route.tail
+ }
+
+ /**
+ * Finds the strongly-connected components of the directed DepNode graph
+ * by finding cycles in a Depth-First manner and collapsing any components
+ * whose nodes are involved in the cycle.
+ */
+ def stronglyConnectedComponents(nodes: Seq[DepNode]): Seq[Seq[DepNode]] = {
+
+ val nodeMap = nodes.map(n => n.value -> n).toMap
+
+ val components = mutable.Map.empty[DepNode, Int] ++ nodes.zipWithIndex.toMap
+ val visited = mutable.Set.empty[DepNode]
+
+ nodes.foreach(n => rec(n, Nil))
+
+ def rec(node: DepNode, path: List[DepNode]): Unit = {
+ if (path.exists(components(_) == components(node))) {
+ val cycle = path.reverse
+ .dropWhile(components(_) != components(node))
+
+ val involved = cycle.map(components)
+ val firstIndex = involved.head
+ for ((n, i) <- components.toSeq){
+ if (involved.contains(i)){
+ components(n) = firstIndex
+ }
+ }
+ } else if (!visited(node)) {
+ visited.add(node)
+ // sketchy sorting to make sure we're doing this deterministically...
+ for((key, lines) <- node.dependencies.toSeq.sortBy(_._1.toString)){
+ rec(nodeMap(key), node :: path)
+ }
+ }
+ }
+
+ components.groupBy{case (node, i) => i}
+ .toSeq
+ .sortBy(_._1)
+ .map(_._2.keys.toSeq)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/Plugin.scala b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/Plugin.scala
new file mode 100644
index 00000000..257894c9
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/Plugin.scala
@@ -0,0 +1,26 @@
+package acyclic.plugin
+import acyclic.file
+import tools.nsc.Global
+import scala.collection.SortedSet
+
+class RuntimePlugin(global: Global) extends TestPlugin(global)
+class TestPlugin(val global: Global,
+ cycleReporter: Seq[(Value, SortedSet[Int])] => Unit = _ => ())
+ extends tools.nsc.plugins.Plugin {
+
+ val name = "acyclic"
+
+ var force = false
+ // Yeah processOptions is deprecated but keep using it anyway for 2.10.x compatibility
+ override def processOptions(options: List[String], error: String => Unit): Unit = {
+ if (options.contains("force")) {
+ force = true
+ }
+ }
+ val description = "Allows the developer to prohibit inter-file dependencies"
+
+
+ val components = List[tools.nsc.plugins.PluginComponent](
+ new PluginPhase(this.global, cycleReporter, force)
+ )
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/PluginPhase.scala b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/PluginPhase.scala
new file mode 100644
index 00000000..eaee91a7
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/main/scala/acyclic/plugin/PluginPhase.scala
@@ -0,0 +1,180 @@
+
+package acyclic.plugin
+import acyclic.file
+import scala.collection.{SortedSet, mutable}
+import scala.tools.nsc.{Global, Phase}
+import tools.nsc.plugins.PluginComponent
+
+/**
+ * - Break dependency graph into strongly connected components
+ * - Turn acyclic packages into virtual "files" in the dependency graph, as
+ * aggregates of all the files within them
+ * - Any strongly connected component which includes an acyclic.file or
+ * acyclic.pkg is a failure
+ * - Pick an arbitrary cycle and report it
+ * - Don't report more than one cycle per file/pkg, to avoid excessive spam
+ */
+class PluginPhase(val global: Global,
+ cycleReporter: Seq[(Value, SortedSet[Int])] => Unit,
+ force: => Boolean)
+ extends PluginComponent
+ with GraphAnalysis { t =>
+
+ import global._
+
+ val runsAfter = List("typer")
+
+ override val runsBefore = List("patmat")
+
+ val phaseName = "acyclic"
+ def pkgName(unit: CompilationUnit) = {
+ unit.body
+ .collect{case x: PackageDef => x.pid.toString}
+ .flatMap(_.split('.'))
+ }
+
+ def units = global.currentRun
+ .units
+ .toSeq
+ .sortBy(_.source.content.mkString.hashCode())
+
+ def findAcyclics() = {
+ val acyclicNodePaths = for {
+ unit <- units
+ if unit.body.children.collect{
+ case Import(expr, List(sel)) =>
+ expr.symbol.toString == "package acyclic" && sel.name.toString == "file"
+ }.exists(x => x)
+ } yield {
+ Value.File(unit.source.path, pkgName(unit))
+ }
+ val skipNodePaths = for {
+ unit <- units
+ if unit.body.children.collect{
+ case Import(expr, List(sel)) =>
+ expr.symbol.toString == "package acyclic" && sel.name.toString == "skipped"
+ }.exists(x => x)
+ } yield {
+ Value.File(unit.source.path, pkgName(unit))
+ }
+
+ val acyclicPkgNames = for {
+ unit <- units
+ pkgObject <- unit.body.collect{case x: ModuleDef if x.name.toString == "package" => x }
+ if pkgObject.impl.children.collect{case Import(expr, List(sel)) =>
+ expr.symbol.toString == "package acyclic" && sel.name.toString == "pkg"
+ }.exists(x => x)
+ } yield {
+ Value.Pkg(
+ pkgObject.symbol
+ .enclosingPackageClass
+ .fullName
+ .split('.')
+ .toList
+ )
+ }
+ (skipNodePaths, acyclicNodePaths, acyclicPkgNames)
+ }
+
+ override def newPhase(prev: Phase): Phase = new Phase(prev) {
+ override def run() {
+ val unitMap = units.map(u => u.source.path -> u).toMap
+ val nodes = for (unit <- units) yield {
+
+ val deps = DependencyExtraction(t.global)(unit)
+
+ val connections = for{
+ (sym, tree) <- deps
+ if sym != NoSymbol
+ if sym.sourceFile != null
+ if sym.sourceFile.path != unit.source.path
+ } yield (sym.sourceFile.path, tree)
+
+ Node[Value.File](
+ Value.File(unit.source.path, pkgName(unit)),
+ connections.groupBy(c => Value.File(c._1, pkgName(unitMap(c._1))): Value)
+ .mapValues(_.map(_._2))
+ )
+ }
+
+ val nodeMap = nodes.map(n => n.value -> n).toMap
+
+ val (skipNodePaths, acyclicFiles, acyclicPkgs) = findAcyclics()
+
+ val allAcyclics = acyclicFiles ++ acyclicPkgs
+
+ // synthetic nodes for packages, which aggregate the dependencies of
+ // their contents
+ val pkgNodes = acyclicPkgs.map{ value =>
+ Node(
+ value,
+ nodes.filter(_.value.pkg.startsWith(value.pkg))
+ .flatMap(_.dependencies.toSeq)
+ .groupBy(_._1)
+ .mapValues(_.flatMap(_._2))
+ )
+ }
+
+ val linkedNodes: Seq[DepNode] = (nodes ++ pkgNodes).map{ d =>
+ val extraLinks = for{
+ (value: Value.File, pos) <- d.dependencies
+ acyclicPkg <- acyclicPkgs
+ if nodeMap(value).value.pkg.startsWith(acyclicPkg.pkg)
+ if !d.value.pkg.startsWith(acyclicPkg.pkg)
+ } yield (acyclicPkg, pos)
+ d.copy(dependencies = d.dependencies ++ extraLinks)
+ }
+
+ // only care about cycles with size > 1 here
+ val components = DepNode.stronglyConnectedComponents(linkedNodes)
+ .filter(_.size > 1)
+
+ val usedNodes = mutable.Set.empty[DepNode]
+ for{
+ c <- components
+ n <- c
+ if !usedNodes.contains(n)
+ if (!force && allAcyclics.contains(n.value)) || (force && !skipNodePaths.contains(n.value))
+ }{
+ val cycle = DepNode.smallestCycle(n, c)
+ val cycleInfo =
+ (cycle :+ cycle.head).sliding(2)
+ .map{ case Seq(a, b) => (a.value, a.dependencies(b.value))}
+ .toSeq
+ cycleReporter(
+ cycleInfo.map{ case (a, b) => a -> b.map(_.pos.line).to[SortedSet]}
+ )
+
+ global.error("Unwanted cyclic dependency")
+ for (Seq((value, locs), (nextValue, _)) <- (cycleInfo :+ cycleInfo.head).sliding(2)){
+ global.inform("")
+ value match{
+ case Value.Pkg(pkg) => global.inform(s"package ${pkg.mkString(".")}")
+ case Value.File(_, _) =>
+ }
+
+ units.find(_.source.path == locs.head.pos.source.path)
+ .get
+ .echo(locs.head.pos, "")
+
+ val otherLines = locs.tail
+ .map(_.pos.line)
+ .filter(_ != locs.head.pos.line)
+
+ global.inform("symbol: " + locs.head.symbol.toString)
+
+ if (!otherLines.isEmpty){
+ global.inform("More dependencies at lines " + otherLines.mkString(" "))
+ }
+
+ }
+ global.inform("")
+ usedNodes ++= cycle
+ }
+ }
+
+ def name: String = "acyclic"
+ }
+
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/A.scala
new file mode 100644
index 00000000..a0ff0100
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/A.scala
@@ -0,0 +1,6 @@
+package fail.cyclicgraph
+import acyclic.file
+
+class A{
+ val e = new E
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/B.scala
new file mode 100644
index 00000000..d1004f5a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/B.scala
@@ -0,0 +1,6 @@
+package fail.cyclicgraph
+import acyclic.file
+
+class B {
+ val a: A = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/C.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/C.scala
new file mode 100644
index 00000000..9aebe3a0
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/C.scala
@@ -0,0 +1,6 @@
+package fail.cyclicgraph
+import acyclic.file
+
+object C extends A{
+ val a: A = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/D.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/D.scala
new file mode 100644
index 00000000..9c148b0a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/D.scala
@@ -0,0 +1,7 @@
+package fail.cyclicgraph
+import acyclic.file
+
+class D {
+ val b: A = null
+ val c = C
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/E.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/E.scala
new file mode 100644
index 00000000..00551a06
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicgraph/E.scala
@@ -0,0 +1,7 @@
+package fail.cyclicgraph
+import acyclic.file
+
+class E {
+ val a: A = null
+ val d = new D
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A1.scala
new file mode 100644
index 00000000..530e7820
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A1.scala
@@ -0,0 +1,7 @@
+package fail.cyclicpackage
+package a
+import acyclic.file
+
+class A1 extends b.B1{
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A2.scala
new file mode 100644
index 00000000..95606566
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/A2.scala
@@ -0,0 +1,4 @@
+package fail.cyclicpackage.a
+class A2 {
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/package.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/package.scala
new file mode 100644
index 00000000..9ee69111
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/a/package.scala
@@ -0,0 +1,5 @@
+package fail.cyclicpackage
+
+package object a {
+ import acyclic.pkg
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B1.scala
new file mode 100644
index 00000000..9b9de725
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B1.scala
@@ -0,0 +1,3 @@
+package fail.cyclicpackage.b
+import acyclic.file
+class B1 \ No newline at end of file
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B2.scala
new file mode 100644
index 00000000..87cabd93
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/B2.scala
@@ -0,0 +1,5 @@
+package fail.cyclicpackage
+package b
+import acyclic.file
+
+class B2 extends a.A2 \ No newline at end of file
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/package.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/package.scala
new file mode 100644
index 00000000..5f6d9041
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/cyclicpackage/b/package.scala
@@ -0,0 +1,5 @@
+package fail.cyclicpackage
+
+package object b {
+ import acyclic.pkg
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/A.scala
new file mode 100644
index 00000000..d8d118b6
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/A.scala
@@ -0,0 +1,5 @@
+package fail.halfpackagecycle
+
+class A {
+ val thing = c.C1
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/B.scala
new file mode 100644
index 00000000..114d6197
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/B.scala
@@ -0,0 +1,3 @@
+package fail.halfpackagecycle
+
+class B extends A
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C1.scala
new file mode 100644
index 00000000..be4eecf8
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C1.scala
@@ -0,0 +1,3 @@
+package fail.halfpackagecycle.c
+
+object C1
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C2.scala
new file mode 100644
index 00000000..be3e0c63
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/C2.scala
@@ -0,0 +1,6 @@
+package fail.halfpackagecycle
+package c
+
+class C2 {
+ lazy val b = new B
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/package.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/package.scala
new file mode 100644
index 00000000..295a9e7a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/halfpackagecycle/c/package.scala
@@ -0,0 +1,5 @@
+package fail.halfpackagecycle
+
+package object c {
+ import acyclic.pkg
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/A.scala
new file mode 100644
index 00000000..ec4fa106
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/A.scala
@@ -0,0 +1,7 @@
+package fail.indirect
+import acyclic.file
+
+object A
+class A {
+ val b: B = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/B.scala
new file mode 100644
index 00000000..f9f8450a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/B.scala
@@ -0,0 +1,3 @@
+package fail.indirect
+
+class B extends C
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/C.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/C.scala
new file mode 100644
index 00000000..986baaf3
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/indirect/C.scala
@@ -0,0 +1,5 @@
+package fail.indirect
+
+class C {
+ val a = A
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/A.scala
new file mode 100644
index 00000000..e1f95ae9
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/A.scala
@@ -0,0 +1,7 @@
+package fail.simple
+import acyclic.file
+
+
+class A {
+ val b: B = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/B.scala
new file mode 100644
index 00000000..fa9ee63f
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/fail/simple/B.scala
@@ -0,0 +1,6 @@
+package fail.simple
+
+class B {
+ val a1: A = new A
+ val a2: A = new A
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/A.scala
new file mode 100644
index 00000000..24a2a633
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/A.scala
@@ -0,0 +1,7 @@
+package force.simple
+
+
+
+class A {
+ val b: B = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/B.scala
new file mode 100644
index 00000000..50c5d305
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/simple/B.scala
@@ -0,0 +1,6 @@
+package force.simple
+
+class B {
+ val a1: A = new A
+ val a2: A = new A
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/A.scala
new file mode 100644
index 00000000..3f2464cd
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/A.scala
@@ -0,0 +1,7 @@
+package force.skip
+import acyclic.skipped
+
+
+class A {
+ val b: B = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/B.scala
new file mode 100644
index 00000000..b00c6db2
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/force/skip/B.scala
@@ -0,0 +1,6 @@
+package force.skip
+import acyclic.skipped
+class B {
+ val a1: A = new A
+ val a2: A = new A
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/A.scala
new file mode 100644
index 00000000..902ee5fe
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/A.scala
@@ -0,0 +1,5 @@
+package success.cyclicunmarked
+
+class A {
+ val b: B = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/B.scala
new file mode 100644
index 00000000..203707ed
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/cyclicunmarked/B.scala
@@ -0,0 +1,6 @@
+package success.cyclicunmarked
+
+class B {
+ val a1: A = new A
+ val a2: A = new A
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/A.scala
new file mode 100644
index 00000000..c9a27490
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/A.scala
@@ -0,0 +1,4 @@
+package success.dag
+
+class A {
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/B.scala
new file mode 100644
index 00000000..3858e677
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/B.scala
@@ -0,0 +1,5 @@
+package success.dag
+
+class B {
+ val a: A = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/C.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/C.scala
new file mode 100644
index 00000000..c4635adf
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/C.scala
@@ -0,0 +1,3 @@
+package success.dag
+
+object C extends A
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/D.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/D.scala
new file mode 100644
index 00000000..3ab67e39
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/D.scala
@@ -0,0 +1,6 @@
+package success.dag
+
+class D {
+ val b: A = null
+ val c = C
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/E.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/E.scala
new file mode 100644
index 00000000..4148d75a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/dag/E.scala
@@ -0,0 +1,6 @@
+package success.dag
+
+class E {
+ val a: A = null
+ val d = new D
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/java/SomeJava.java b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/java/SomeJava.java
new file mode 100644
index 00000000..cad93696
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/java/SomeJava.java
@@ -0,0 +1,4 @@
+
+public interface SomeJava {
+
+} \ No newline at end of file
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A1.scala
new file mode 100644
index 00000000..3d5bc5b3
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A1.scala
@@ -0,0 +1,6 @@
+package success.halfacyclicpackage
+package a
+
+class A1 extends b.B1{
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A2.scala
new file mode 100644
index 00000000..88ee4a03
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/A2.scala
@@ -0,0 +1,5 @@
+package success.halfacyclicpackage.a
+
+class A2 {
+
+ }
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/package.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/package.scala
new file mode 100644
index 00000000..54f98aff
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/a/package.scala
@@ -0,0 +1,5 @@
+package success.halfacyclicpackage
+
+package object a {
+ import acyclic.pkg
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B1.scala
new file mode 100644
index 00000000..074f808a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B1.scala
@@ -0,0 +1,5 @@
+package success.halfacyclicpackage.b
+
+class B1 {
+
+ }
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B2.scala
new file mode 100644
index 00000000..6e4dfdd5
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/halfacyclic/b/B2.scala
@@ -0,0 +1,6 @@
+package success.halfacyclicpackage
+package b
+
+class B2 extends a.A2{
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A1.scala
new file mode 100644
index 00000000..583e6c68
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A1.scala
@@ -0,0 +1,6 @@
+package success.pkg.innercycle.a
+
+class A1 {
+ val x: A2 = null
+ def y = p
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A2.scala
new file mode 100644
index 00000000..65f656a4
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/A2.scala
@@ -0,0 +1,6 @@
+package success.pkg.innercycle.a
+
+class A2 {
+ val x: A1 = null
+ def z = p
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/package.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/package.scala
new file mode 100644
index 00000000..165fda66
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/innercycle/a/package.scala
@@ -0,0 +1,6 @@
+package success.pkg.innercycle
+
+package object a {
+ val p: A1 with A2 = null
+ import acyclic.pkg
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A1.scala
new file mode 100644
index 00000000..3158f120
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A1.scala
@@ -0,0 +1,7 @@
+package success.cyclicpackage
+package a
+
+
+class A1 extends b.B1{
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A2.scala
new file mode 100644
index 00000000..1c36fe2a
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/a/A2.scala
@@ -0,0 +1,6 @@
+package success.cyclicpackage.a
+
+
+class A2 {
+
+ }
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B1.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B1.scala
new file mode 100644
index 00000000..33e10fc1
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B1.scala
@@ -0,0 +1,6 @@
+package success.cyclicpackage.b
+
+
+class B1 {
+
+ }
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B2.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B2.scala
new file mode 100644
index 00000000..57e324ce
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/mutualcyclic/b/B2.scala
@@ -0,0 +1,7 @@
+package success.cyclicpackage
+package b
+
+
+class B2 extends a.A2{
+
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/single/pkg/package.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/single/pkg/package.scala
new file mode 100644
index 00000000..c39b5e62
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/pkg/single/pkg/package.scala
@@ -0,0 +1,5 @@
+package success.singlepackage
+
+package object pkg {
+ import acyclic.pkg
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/A.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/A.scala
new file mode 100644
index 00000000..24b9d0d3
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/A.scala
@@ -0,0 +1,4 @@
+package success.simple
+
+class A {
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/B.scala b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/B.scala
new file mode 100644
index 00000000..b7ca5335
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/resources/success/simple/B.scala
@@ -0,0 +1,5 @@
+package success.simple
+
+class B {
+ val a: A = null
+}
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/CycleTests.scala b/scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/CycleTests.scala
new file mode 100644
index 00000000..ff831aad
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/CycleTests.scala
@@ -0,0 +1,68 @@
+package acyclic
+
+import utest._
+import TestUtils.{make, makeFail}
+import scala.tools.nsc.util.ScalaClassLoader.URLClassLoader
+import acyclic.plugin.Value.{Pkg, File}
+import scala.collection.SortedSet
+import acyclic.file
+
+object CycleTests extends TestSuite{
+
+ def tests = TestSuite{
+ 'fail{
+ 'simple-makeFail("fail/simple")(Seq(
+ File("B.scala") -> SortedSet(4, 5),
+ File("A.scala") -> SortedSet(6)
+ ))
+
+ 'indirect-makeFail("fail/indirect")(Seq(
+ File("A.scala") -> SortedSet(6),
+ File("B.scala") -> SortedSet(3),
+ File("C.scala") -> SortedSet(4)
+ ))
+ 'cyclicgraph-makeFail("fail/cyclicgraph")(
+ Seq(
+ File("A.scala") -> SortedSet(5),
+ File("E.scala") -> SortedSet(6),
+ File("D.scala") -> SortedSet(6),
+ File("C.scala") -> SortedSet(4, 5)
+ )
+ )
+ 'cyclicpackage-makeFail("fail/cyclicpackage")(
+ Seq(
+ Pkg("fail.cyclicpackage.b") -> SortedSet(5),
+ Pkg("fail.cyclicpackage.a") -> SortedSet(5)
+ )
+ )
+ 'halfpackagecycle-makeFail("fail/halfpackagecycle")(Seq(
+ File("B.scala") -> SortedSet(3),
+ File("A.scala") -> SortedSet(4),
+ Pkg("fail.halfpackagecycle.c") -> SortedSet(5)
+ ))
+ }
+ 'success{
+ 'simple-make("success/simple")
+ 'ignorejava-make("success/java")
+ 'cyclicunmarked-make("success/cyclicunmarked")
+ 'dag-make("success/dag")
+ 'pkg{
+ "single" - make("success/pkg/single")
+ "mutualcyclic" - make("success/pkg/mutualcyclic")
+ "halfacyclic" - make("success/pkg/halfacyclic")
+ "innercycle" - make("success/pkg/innercycle")
+ }
+ }
+ 'self-make("../../main/scala", extraIncludes = Nil)
+ 'force{
+ 'fail-makeFail("force/simple", force = true)(Seq(
+ File("B.scala") -> SortedSet(4, 5),
+ File("A.scala") -> SortedSet(6)
+ ))
+ 'pass-make("force/simple")
+ 'skip-make("force/skip", force = true)
+ }
+ }
+}
+
+
diff --git a/scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/TestUtils.scala b/scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/TestUtils.scala
new file mode 100644
index 00000000..7bff8248
--- /dev/null
+++ b/scalaplugin/src/test/resource/acyclic/src/test/scala/acyclic/TestUtils.scala
@@ -0,0 +1,92 @@
+package acyclic
+
+import tools.nsc.{Global, Settings}
+import tools.nsc.reporters.ConsoleReporter
+import tools.nsc.plugins.Plugin
+
+import java.net.URLClassLoader
+import scala.tools.nsc.util.ClassPath
+import utest._, asserts._
+import scala.reflect.io.VirtualDirectory
+import acyclic.plugin.Value
+import scala.collection.SortedSet
+
+object TestUtils {
+ def getFilePaths(src: String): List[String] = {
+ val f = new java.io.File(src)
+ if (f.isDirectory) f.list.toList.flatMap(x => getFilePaths(src + "/" + x))
+ else List(src)
+ }
+
+ /**
+ * Attempts to compile a resource folder as a compilation run, in order
+ * to test whether it succeeds or fails correctly.
+ */
+ def make(path: String,
+ extraIncludes: Seq[String] = Seq("src/main/scala/acyclic/package.scala"),
+ force: Boolean = false) = {
+ val src = "src/test/resources/" + path
+ val sources = getFilePaths(src) ++ extraIncludes
+
+ val vd = new VirtualDirectory("(memory)", None)
+ lazy val settings = new Settings
+ val loader = getClass.getClassLoader.asInstanceOf[URLClassLoader]
+ val entries = loader.getURLs map(_.getPath)
+ settings.outputDirs.setSingleOutput(vd)
+
+ // annoyingly, the Scala library is not in our classpath, so we have to add it manually
+ val sclpath = entries.map(
+ _.replaceAll("scala-compiler.jar", "scala-library.jar")
+ )
+
+ settings.classpath.value = ClassPath.join(entries ++ sclpath : _*)
+
+ if (force) settings.pluginOptions.value = List("acyclic:force")
+
+ var cycles: Option[Seq[Seq[(acyclic.plugin.Value, SortedSet[Int])]]] = None
+ lazy val compiler = new Global(settings, new ConsoleReporter(settings)){
+ override protected def loadRoughPluginsList(): List[Plugin] = {
+ List(new plugin.TestPlugin(this, foundCycles => cycles = cycles match{
+ case None => Some(Seq(foundCycles))
+ case Some(oldCycles) => Some(oldCycles :+ foundCycles)
+ }))
+ }
+ }
+ val run = new compiler.Run()
+ run.compile(sources)
+
+ if (vd.toList.isEmpty) throw CompilationException(cycles.get)
+ }
+
+ def makeFail(path: String, force: Boolean = false)(expected: Seq[(Value, SortedSet[Int])]*) = {
+ def canonicalize(cycle: Seq[(Value, SortedSet[Int])]): Seq[(Value, SortedSet[Int])] = {
+ val startIndex = cycle.indexOf(cycle.minBy(_._1.toString))
+ cycle.toList.drop(startIndex) ++ cycle.toList.take(startIndex)
+ }
+
+ val ex = intercept[CompilationException]{ make(path, force = force) }
+ val cycles = ex.cycles
+ .map(canonicalize)
+ .map(
+ _.map{
+ case (Value.File(p, pkg), v) => (Value.File(p, Nil), v)
+ case x => x
+ }
+ )
+ .toSet
+
+ def expand(v: Value) = v match{
+ case Value.File(filePath, pkg) => Value.File("src/test/resources/" + path + "/" + filePath, Nil)
+ case v => v
+ }
+
+ val fullExpected = expected.map(_.map(x => x.copy(_1 = expand(x._1))))
+ .map(canonicalize)
+ .toSet
+
+ assert(fullExpected.forall(cycles.contains))
+ }
+
+ case class CompilationException(cycles: Seq[Seq[(Value, SortedSet[Int])]]) extends Exception
+
+}