diff options
Diffstat (limited to 'scalalib')
-rw-r--r-- | scalalib/src/mill/scalalib/JavaModule.scala | 6 | ||||
-rw-r--r-- | scalalib/src/mill/scalalib/Lib.scala | 144 | ||||
-rw-r--r-- | scalalib/src/mill/scalalib/ScalaWorkerApi.scala | 18 | ||||
-rw-r--r-- | scalalib/src/mill/scalalib/TestRunner.scala | 130 | ||||
-rw-r--r-- | scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala | 53 |
5 files changed, 151 insertions, 200 deletions
diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala index a05e1778..26c58a8b 100644 --- a/scalalib/src/mill/scalalib/JavaModule.scala +++ b/scalalib/src/mill/scalalib/JavaModule.scala @@ -303,8 +303,8 @@ trait TestModule extends JavaModule with TaskModule { val outputPath = T.ctx().dest/"out.json" Jvm.subprocess( - mainClass = "mill.scalalib.worker.ScalaWorker", - classPath = ScalaWorkerModule.classpath(), + mainClass = "mill.scalalib.TestRunner", + classPath = ScalaWorkerModule.scalalibClasspath().map(_.path), jvmArgs = forkArgs(), envArgs = forkEnv(), mainArgs = @@ -330,7 +330,7 @@ trait TestModule extends JavaModule with TaskModule { def testLocal(args: String*) = T.command{ val outputPath = T.ctx().dest/"out.json" - Lib.runTests( + TestRunner.runTests( TestRunner.frameworks(testFrameworks()), runClasspath().map(_.path), Agg(compile().classes.path), diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala index bda8e8f8..7bd0a5ff 100644 --- a/scalalib/src/mill/scalalib/Lib.scala +++ b/scalalib/src/mill/scalalib/Lib.scala @@ -109,23 +109,12 @@ object Lib{ deps: TraversableOnce[Dep], mapDependencies: Option[Dependency => Dependency] = None) = { val depSeq = deps.toSeq - val flattened = depSeq.map(depToDependency) - - val forceVersions = depSeq.filter(_.force) - .map(depToDependency) - .map(mapDependencies.getOrElse(identity[Dependency](_))) - .map{d => d.module -> d.version} - .toMap - - val start = Resolution( - flattened.map(mapDependencies.getOrElse(identity[Dependency](_))).toSet, - forceVersions = forceVersions, - mapDependencies = mapDependencies + mill.modules.Jvm.resolveDependenciesMetadata( + repositories, + depSeq.map(depToDependency), + depSeq.filter(_.force).map(depToDependency), + mapDependencies ) - - val fetch = Fetch.from(repositories, Cache.fetch()) - val resolution = start.process.run(fetch).unsafePerformSync - (flattened, resolution) } /** * Resolve dependencies using Coursier. @@ -139,54 +128,14 @@ object Lib{ deps: TraversableOnce[Dep], sources: Boolean = false, mapDependencies: Option[Dependency => Dependency] = None): Result[Agg[PathRef]] = { - - val (_, resolution) = resolveDependenciesMetadata( - repositories, depToDependency, deps, mapDependencies + val depSeq = deps.toSeq + mill.modules.Jvm.resolveDependencies( + repositories, + depSeq.map(depToDependency), + depSeq.filter(_.force).map(depToDependency), + sources, + mapDependencies ) - val errs = resolution.metadataErrors - if(errs.nonEmpty) { - val header = - s"""| - |Resolution failed for ${errs.length} modules: - |-------------------------------------------- - |""".stripMargin - - val errLines = errs.map { - case ((module, vsn), errMsgs) => s" ${module.trim}:$vsn \n\t" + errMsgs.mkString("\n\t") - }.mkString("\n") - val msg = header + errLines + "\n" - Result.Failure(msg) - } else { - - def load(artifacts: Seq[coursier.Artifact]) = { - val logger = None - val loadedArtifacts = scalaz.concurrent.Task.gatherUnordered( - for (a <- artifacts) - yield coursier.Cache.file(a, logger = logger).run - .map(a.isOptional -> _) - ).unsafePerformSync - - val errors = loadedArtifacts.collect { - case (false, scalaz.-\/(x)) => x - case (true, scalaz.-\/(x)) if !x.notFound => x - } - val successes = loadedArtifacts.collect { case (_, scalaz.\/-(x)) => x } - (errors, successes) - } - - val sourceOrJar = - if (sources) resolution.classifiersArtifacts(Seq("sources")) - else resolution.artifacts(true) - val (errors, successes) = load(sourceOrJar) - if(errors.isEmpty){ - Agg.from( - successes.map(p => PathRef(Path(p), quick = true)).filter(_.path.ext == "jar") - ) - }else{ - val errorDetails = errors.map(e => s"${Util.newLine} ${e.describe}").mkString - Result.Failure("Failed to load source dependencies" + errorDetails) - } - } } def scalaCompilerIvyDeps(scalaVersion: String) = Agg[Dep]( ivy"org.scala-lang:scala-compiler:$scalaVersion".forceVersion(), @@ -202,75 +151,6 @@ object Lib{ force = false ) - def runTests(frameworkInstances: ClassLoader => Seq[sbt.testing.Framework], - entireClasspath: Agg[Path], - testClassfilePath: Agg[Path], - args: Seq[String]) - (implicit ctx: Ctx.Log with Ctx.Home): (String, Seq[mill.scalalib.TestRunner.Result]) = { - Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, cl => { - val frameworks = frameworkInstances(cl) - - val events = mutable.Buffer.empty[Event] - - val doneMessages = frameworks.map { framework => - val runner = framework.runner(args.toArray, args.toArray, cl) - - val testClasses = discoverTests(cl, framework, testClassfilePath) - - val tasks = runner.tasks( - for ((cls, fingerprint) <- testClasses.toArray) - yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) - ) - - val taskQueue = tasks.to[mutable.Queue] - while (taskQueue.nonEmpty){ - val next = taskQueue.dequeue().execute( - new EventHandler { - def handle(event: Event) = events.append(event) - }, - Array( - new Logger { - def debug(msg: String) = ctx.log.outputStream.println(msg) - - def error(msg: String) = ctx.log.outputStream.println(msg) - - def ansiCodesSupported() = true - - def warn(msg: String) = ctx.log.outputStream.println(msg) - - def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) - - def info(msg: String) = ctx.log.outputStream.println(msg) - }) - ) - taskQueue.enqueue(next:_*) - } - ctx.log.outputStream.println(runner.done()) - } - - val results = for(e <- events) yield { - val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None - mill.scalalib.TestRunner.Result( - e.fullyQualifiedName(), - e.selector() match{ - case s: NestedSuiteSelector => s.suiteId() - case s: NestedTestSelector => s.suiteId() + "." + s.testName() - case s: SuiteSelector => s.toString - case s: TestSelector => s.testName() - case s: TestWildcardSelector => s.testWildcard() - }, - e.duration(), - e.status().toString, - ex.map(_.getClass.getName), - ex.map(_.getMessage), - ex.map(_.getStackTrace) - ) - } - - (doneMessages.mkString("\n"), results) - }) - } - def listClassFiles(base: Path): Iterator[String] = { if (base.isDir) ls.rec(base).toIterator.filter(_.ext == "class").map(_.relativeTo(base).toString) else { diff --git a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala index a01c1c2c..6865c541 100644 --- a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala +++ b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala @@ -22,20 +22,16 @@ trait ScalaWorkerModule extends mill.Module{ ) def classpath = T{ - val scalaWorkerJar = sys.props("MILL_SCALA_WORKER") - if (scalaWorkerJar != null) { - mill.eval.Result.Success(Loose.Agg.from(scalaWorkerJar.split(',').map(Path(_)))) - } else { - resolveDependencies( - repositories, - Lib.depToDependency(_, "2.12.4", ""), - Seq(ivy"com.lihaoyi::mill-scalalib-worker:${sys.props("MILL_VERSION")}") - ).map(_.map(_.path)) - } + mill.modules.Util.millProjectModule("MILL_SCALA_WORKER", "mill-scalalib-worker", repositories) } + + def scalalibClasspath = T{ + mill.modules.Util.millProjectModule("MILL_SCALA_LIB", "mill-scalalib", repositories) + } + def worker: Worker[ScalaWorkerApi] = T.worker{ val cl = mill.util.ClassLoader.create( - classpath().map(_.toNIO.toUri.toURL).toVector, + classpath().map(_.path.toNIO.toUri.toURL).toVector, getClass.getClassLoader ) val cls = cl.loadClass("mill.scalalib.worker.ScalaWorker") diff --git a/scalalib/src/mill/scalalib/TestRunner.scala b/scalalib/src/mill/scalalib/TestRunner.scala index 7bee681f..a014df0c 100644 --- a/scalalib/src/mill/scalalib/TestRunner.scala +++ b/scalalib/src/mill/scalalib/TestRunner.scala @@ -1,7 +1,136 @@ package mill.scalalib +import ammonite.ops.Path +import ammonite.util.Colors +import mill.Agg +import mill.modules.Jvm +import mill.scalalib.Lib.discoverTests +import mill.util.{Ctx, PrintLogger} import mill.util.JsonFormatters._ +import sbt.testing._ + +import scala.collection.mutable object TestRunner { + + def main(args: Array[String]): Unit = { + try{ + var i = 0 + def readArray() = { + val count = args(i).toInt + val slice = args.slice(i + 1, i + count + 1) + i = i + count + 1 + slice + } + val frameworks = readArray() + val classpath = readArray() + val arguments = readArray() + val outputPath = args(i + 0) + val colored = args(i + 1) + val testCp = args(i + 2) + val homeStr = args(i + 3) + val ctx = new Ctx.Log with Ctx.Home { + val log = PrintLogger( + colored == "true", + if(colored == "true") Colors.Default + else Colors.BlackWhite, + System.out, + System.err, + System.err, + System.in + ) + val home = Path(homeStr) + } + val result = runTests( + frameworkInstances = TestRunner.frameworks(frameworks), + entireClasspath = Agg.from(classpath.map(Path(_))), + testClassfilePath = Agg(Path(testCp)), + args = arguments + )(ctx) + + // Clear interrupted state in case some badly-behaved test suite + // dirtied the thread-interrupted flag and forgot to clean up. Otherwise + // that flag causes writing the results to disk to fail + Thread.interrupted() + ammonite.ops.write(Path(outputPath), upickle.default.write(result)) + }catch{case e: Throwable => + println(e) + e.printStackTrace() + } + // Tests are over, kill the JVM whether or not anyone's threads are still running + // Always return 0, even if tests fail. The caller can pick up the detailed test + // results from the outputPath + System.exit(0) + } + + def runTests(frameworkInstances: ClassLoader => Seq[sbt.testing.Framework], + entireClasspath: Agg[Path], + testClassfilePath: Agg[Path], + args: Seq[String]) + (implicit ctx: Ctx.Log with Ctx.Home): (String, Seq[mill.scalalib.TestRunner.Result]) = { + Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, isolated = true, cl => { + val frameworks = frameworkInstances(cl) + + val events = mutable.Buffer.empty[Event] + + val doneMessages = frameworks.map{ framework => + val runner = framework.runner(args.toArray, args.toArray, cl) + + val testClasses = discoverTests(cl, framework, testClassfilePath) + + val tasks = runner.tasks( + for ((cls, fingerprint) <- testClasses.toArray) + yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) + ) + + val taskQueue = tasks.to[mutable.Queue] + while (taskQueue.nonEmpty){ + val next = taskQueue.dequeue().execute( + new EventHandler { + def handle(event: Event) = events.append(event) + }, + Array( + new Logger { + def debug(msg: String) = ctx.log.outputStream.println(msg) + + def error(msg: String) = ctx.log.outputStream.println(msg) + + def ansiCodesSupported() = true + + def warn(msg: String) = ctx.log.outputStream.println(msg) + + def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) + + def info(msg: String) = ctx.log.outputStream.println(msg) + }) + ) + taskQueue.enqueue(next:_*) + } + runner.done() + } + + val results = for(e <- events) yield { + val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None + mill.scalalib.TestRunner.Result( + e.fullyQualifiedName(), + e.selector() match{ + case s: NestedSuiteSelector => s.suiteId() + case s: NestedTestSelector => s.suiteId() + "." + s.testName() + case s: SuiteSelector => s.toString + case s: TestSelector => s.testName() + case s: TestWildcardSelector => s.testWildcard() + }, + e.duration(), + e.status().toString, + ex.map(_.getClass.getName), + ex.map(_.getMessage), + ex.map(_.getStackTrace) + ) + } + + (doneMessages.mkString("\n"), results) + }) + } + def frameworks(frameworkNames: Seq[String])(cl: ClassLoader): Seq[sbt.testing.Framework] = { frameworkNames.map { name => cl.loadClass(name).newInstance().asInstanceOf[sbt.testing.Framework] @@ -19,5 +148,4 @@ object TestRunner { object Result{ implicit def resultRW: upickle.default.ReadWriter[Result] = upickle.default.macroRW[Result] } - } diff --git a/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala b/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala index cebef9b7..9b14757c 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala @@ -23,59 +23,6 @@ case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClassp Locate.definesClass(classpathEntry) } -object ScalaWorker{ - - def main(args: Array[String]): Unit = { - try{ - var i = 0 - def readArray() = { - val count = args(i).toInt - val slice = args.slice(i + 1, i + count + 1) - i = i + count + 1 - slice - } - val frameworks = readArray() - val classpath = readArray() - val arguments = readArray() - val outputPath = args(i + 0) - val colored = args(i + 1) - val testCp = args(i + 2) - val homeStr = args(i + 3) - val ctx = new Ctx.Log with Ctx.Home { - val log = PrintLogger( - colored == "true", - if(colored == "true") Colors.Default - else Colors.BlackWhite, - System.out, - System.err, - System.err, - System.in - ) - val home = Path(homeStr) - } - val result = Lib.runTests( - frameworkInstances = TestRunner.frameworks(frameworks), - entireClasspath = Agg.from(classpath.map(Path(_))), - testClassfilePath = Agg(Path(testCp)), - args = arguments - )(ctx) - - // Clear interrupted state in case some badly-behaved test suite - // dirtied the thread-interrupted flag and forgot to clean up. Otherwise - // that flag causes writing the results to disk to fail - Thread.interrupted() - ammonite.ops.write(Path(outputPath), upickle.default.write(result)) - }catch{case e: Throwable => - println(e) - e.printStackTrace() - } - // Tests are over, kill the JVM whether or not anyone's threads are still running - // Always return 0, even if tests fail. The caller can pick up the detailed test - // results from the outputPath - System.exit(0) - } -} - class ScalaWorker(ctx0: mill.util.Ctx, compilerBridgeClasspath: Array[String]) extends mill.scalalib.ScalaWorkerApi{ @volatile var scalaClassloaderCache = Option.empty[(Long, ClassLoader)] |