diff --git a/integration/feature/docannotations/resources/build.mill b/integration/feature/docannotations/resources/build.mill index 6a12c87c5fb..d70236c0320 100644 --- a/integration/feature/docannotations/resources/build.mill +++ b/integration/feature/docannotations/resources/build.mill @@ -11,13 +11,27 @@ trait JUnitTests extends TestModule.Junit4 { def task = Task { "???" } + + /** + * *The worker* + */ + def theWorker = Task.Worker { + () + } } /** * The Core Module Docz! */ object core extends JavaModule { - object test extends JavaTests with JUnitTests + object test extends JavaTests with JUnitTests { + /** + * -> The worker <- + */ + def theWorker = Task.Worker { + () + } + } /** * Core Target Docz! diff --git a/integration/feature/docannotations/src/DocAnnotationsTests.scala b/integration/feature/docannotations/src/DocAnnotationsTests.scala index bfd237a9ae8..81ad43a2733 100644 --- a/integration/feature/docannotations/src/DocAnnotationsTests.scala +++ b/integration/feature/docannotations/src/DocAnnotationsTests.scala @@ -113,6 +113,22 @@ object DocAnnotationsTests extends UtestIntegrationTestSuite { ) ) + assert(eval(("inspect", "core.test.theWorker")).isSuccess) + val theWorkerInspect = out("inspect").json.str + + assert( + globMatches( + """core.test.theWorker(build.mill:...) + | -> The worker <- + | + | *The worker* + | + |Inputs: + |""".stripMargin, + theWorkerInspect + ) + ) + // Make sure both kebab-case and camelCase flags work, even though the // docs from `inspect` only show the kebab-case version assert(eval(("core.ivyDepsTree", "--withCompile", "--withRuntime")).isSuccess) diff --git a/main/eval/src/mill/eval/GroupEvaluator.scala b/main/eval/src/mill/eval/GroupEvaluator.scala index 7d1529801ea..dcdcf2e7695 100644 --- a/main/eval/src/mill/eval/GroupEvaluator.scala +++ b/main/eval/src/mill/eval/GroupEvaluator.scala @@ -364,28 +364,36 @@ private[mill] trait GroupEvaluator { inputsHash: Int, labelled: Terminal.Labelled[_] ): Unit = { - labelled.task.asWorker match { - case Some(w) => - workerCache.synchronized { - workerCache.update(w.ctx.segments, (workerCacheHash(inputsHash), v)) - } - case None => - val terminalResult = labelled - .task - .writerOpt - .asInstanceOf[Option[upickle.default.Writer[Any]]] - .map { w => upickle.default.writeJs(v.value)(w) } - - for (json <- terminalResult) { - os.write.over( - metaPath, - upickle.default.stream( - Evaluator.Cached(json, hashCode, inputsHash), - indent = 4 - ), - createFolders = true + for (w <- labelled.task.asWorker) + workerCache.synchronized { + workerCache.update(w.ctx.segments, (workerCacheHash(inputsHash), v)) + } + + val terminalResult = labelled + .task + .writerOpt + .map { w => + upickle.default.writeJs(v.value)(w.asInstanceOf[upickle.default.Writer[Any]]) + } + .orElse { + labelled.task.asWorker.map { w => + ujson.Obj( + "worker" -> ujson.Str(labelled.segments.render), + "toString" -> ujson.Str(v.value.toString), + "inputsHash" -> ujson.Num(inputsHash) ) } + } + + for (json <- terminalResult) { + os.write.over( + metaPath, + upickle.default.stream( + Evaluator.Cached(json, hashCode, inputsHash), + indent = 4 + ), + createFolders = true + ) } } diff --git a/main/resolve/src/mill/resolve/Resolve.scala b/main/resolve/src/mill/resolve/Resolve.scala index 09c0edc6590..f011ca46b29 100644 --- a/main/resolve/src/mill/resolve/Resolve.scala +++ b/main/resolve/src/mill/resolve/Resolve.scala @@ -41,10 +41,10 @@ object Resolve { allowPositionalCommandArgs: Boolean ) = { val taskList = resolved.map { - case r: Resolved.Target => + case r: Resolved.NamedTask => val instantiated = ResolveCore .instantiateModule(rootModule, r.segments.init) - .flatMap(instantiateTarget(r, _)) + .flatMap(instantiateNamedTask(r, _)) instantiated.map(Some(_)) @@ -76,7 +76,7 @@ object Resolve { directChildrenOrErr.flatMap(directChildren => directChildren.head match { - case r: Resolved.Target => instantiateTarget(r, value).map(Some(_)) + case r: Resolved.NamedTask => instantiateNamedTask(r, value).map(Some(_)) case r: Resolved.Command => instantiateCommand( rootModule, @@ -104,13 +104,16 @@ object Resolve { items.distinctBy(_.ctx.segments) } - private def instantiateTarget(r: Resolved.Target, p: Module): Either[String, Target[_]] = { + private def instantiateNamedTask( + r: Resolved.NamedTask, + p: Module + ): Either[String, NamedTask[_]] = { val definition = Reflect - .reflect(p.getClass, classOf[Target[_]], _ == r.segments.parts.last, true) + .reflect(p.getClass, classOf[NamedTask[_]], _ == r.segments.parts.last, true) .head ResolveCore.catchWrapException( - definition.invoke(p).asInstanceOf[Target[_]] + definition.invoke(p).asInstanceOf[NamedTask[_]] ) } diff --git a/main/resolve/src/mill/resolve/ResolveCore.scala b/main/resolve/src/mill/resolve/ResolveCore.scala index 31a7d632f39..5c3a7396a74 100644 --- a/main/resolve/src/mill/resolve/ResolveCore.scala +++ b/main/resolve/src/mill/resolve/ResolveCore.scala @@ -29,7 +29,7 @@ private object ResolveCore { object Resolved { case class Module(segments: Segments, cls: Class[_]) extends Resolved - case class Target(segments: Segments) extends Resolved + case class NamedTask(segments: Segments) extends Resolved case class Command(segments: Segments) extends Resolved } @@ -327,7 +327,7 @@ private object ResolveCore { .map( _.map { case (Resolved.Module(s, cls), _) => Resolved.Module(segments ++ s, cls) - case (Resolved.Target(s), _) => Resolved.Target(segments ++ s) + case (Resolved.NamedTask(s), _) => Resolved.NamedTask(segments ++ s) case (Resolved.Command(s), _) => Resolved.Command(segments ++ s) } .toSet @@ -376,10 +376,10 @@ private object ResolveCore { } } - val targets = Reflect - .reflect(cls, classOf[Target[_]], namePred, noParams = true) + val namedTasks = Reflect + .reflect(cls, classOf[NamedTask[_]], namePred, noParams = true) .map { m => - Resolved.Target(Segments.labels(decode(m.getName))) -> + Resolved.NamedTask(Segments.labels(decode(m.getName))) -> None } @@ -388,7 +388,7 @@ private object ResolveCore { .map(m => decode(m.getName)) .map { name => Resolved.Command(Segments.labels(name)) -> None } - modulesOrErr.map(_ ++ targets ++ commands) + modulesOrErr.map(_ ++ namedTasks ++ commands) } def notFoundResult( diff --git a/main/test/src/mill/main/MainModuleTests.scala b/main/test/src/mill/main/MainModuleTests.scala index 6f5b552ccdf..82d0790c7bc 100644 --- a/main/test/src/mill/main/MainModuleTests.scala +++ b/main/test/src/mill/main/MainModuleTests.scala @@ -27,6 +27,18 @@ object MainModuleTests extends TestSuite { Map("1" -> "hello", "2" -> "world") } def helloCommand(x: Int, y: Task[String]) = Task.Command { (x, y(), hello()) } + + /** + * The hello worker + */ + def helloWorker = Task.Worker { + // non-JSON-serializable, but everything should work fine nonetheless + new AutoCloseable { + def close() = () + override def toString = + "theHelloWorker" + } + } override lazy val millDiscover: Discover = Discover[this.type] } @@ -93,6 +105,17 @@ object MainModuleTests extends TestSuite { res.contains("hello") ) } + test("worker") - UnitTester(mainModule, null).scoped { eval => + val Right(result) = eval.apply("inspect", "helloWorker") + + val Seq(res: String) = result.value + assert( + res.startsWith("helloWorker("), + res.contains("MainModuleTests.scala:"), + res.contains("The hello worker"), + res.contains("hello") + ) + } } test("show") { @@ -173,6 +196,14 @@ object MainModuleTests extends TestSuite { val Seq(res) = result.value assert(res == ujson.Arr(1337, "lol", ujson.Arr("hello", "world"))) } + + test("worker") { + val Right(result) = evaluator.apply("show", "helloWorker") + val Seq(res: ujson.Obj) = result.value + assert(res("toString").str == "theHelloWorker") + assert(res("worker").str == "helloWorker") + assert(res("inputsHash").numOpt.isDefined) + } } test("showNamed") { @@ -209,6 +240,18 @@ object MainModuleTests extends TestSuite { } } + test("resolve") { + UnitTester(mainModule, null).scoped { eval => + val Right(result) = eval.apply("resolve", "_") + + val Seq(res: Seq[String]) = result.value + assert(res.contains("hello")) + assert(res.contains("hello2")) + assert(res.contains("helloCommand")) + assert(res.contains("helloWorker")) + } + } + test("clean") { val ev = UnitTester(cleanModule, null) val out = ev.evaluator.outPath