From 3abfa6b6a4511824ab933e7e3d9eb7a737785af0 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 23 Apr 2023 13:03:58 +0800 Subject: [PATCH 01/57] add module-init-error test --- .../failure/module-init-error/repo/build.sc | 17 ++++++++ .../test/src/ModuleInitErrorTests.scala | 41 +++++++++++++++++++ main/core/src/mill/define/Discover.scala | 2 + main/core/src/mill/define/Module.scala | 8 ++-- main/src/mill/main/ResolveSegments.scala | 4 +- .../src/mill/runner/MillBuildBootstrap.scala | 4 +- 6 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 integration/failure/module-init-error/repo/build.sc create mode 100644 integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala diff --git a/integration/failure/module-init-error/repo/build.sc b/integration/failure/module-init-error/repo/build.sc new file mode 100644 index 00000000000..37a58bc1706 --- /dev/null +++ b/integration/failure/module-init-error/repo/build.sc @@ -0,0 +1,17 @@ +import mill._, scalalib._ + +def rootTarget = T{ println("rooty"); "rooty2" } + +object foo extends Module{ + def fooTarget = T{ 123 } + throw new Exception("Foo Boom") +} + +object bar extends Module { + def barTarget = T { "abc" } + + object Qux extends Module{ + def quxTarget = T { "xyz" } + throw new Exception("Qux Boom") + } +} diff --git a/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala b/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala new file mode 100644 index 00000000000..d0cb6b8e423 --- /dev/null +++ b/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala @@ -0,0 +1,41 @@ +package mill.integration + +import mill.util.Util +import utest._ + +object ModuleInitErrorTests extends IntegrationTestSuite { + def captureOutErr = true + val tests = Tests { + initWorkspace() + + test("root") { + // If we specify a target in the root module, we are not + // affected by the sub-modules failing to initialize + val res = evalStdout("rootTarget") + + assert(res.isSuccess == true) + assert(res.out.contains("""rooty""")) + + val res2 = evalStdout("show", "rootTarget") + + assert(res2.isSuccess == true) + assert(res2.out.contains(""""rooty2"""")) + } + test("foo") { + println("foo 1") + val res = evalStdout("foo.fooTarget") + println("foo 2") + + assert(res.isSuccess == false) + println("foo 3") + assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Foo Boom""")) + println("foo 4") + } + test("barQux") { + val res = evalStdout("bar.qux.quxTarget") + + assert(res.isSuccess == false) + assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Qux Boom""")) + } + } +} diff --git a/main/core/src/mill/define/Discover.scala b/main/core/src/mill/define/Discover.scala index 5bd44065df1..af18c181636 100644 --- a/main/core/src/mill/define/Discover.scala +++ b/main/core/src/mill/define/Discover.scala @@ -1,5 +1,7 @@ package mill.define +import mill.api.internal + import language.experimental.macros import scala.collection.mutable import scala.reflect.macros.blackbox diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 060b7a4b79c..713668ca70f 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -26,7 +26,7 @@ class Module(implicit outerCtx0: mill.define.Ctx) extends mill.moduledefs.Cacher // We keep a private `lazy val` and a public `def` so // subclasses can call `super.millModuleDirectChildren` private lazy val millModuleDirectChildrenImpl: Seq[Module] = - millInternal.reflectNestedObjects[Module].toSeq + millInternal.reflectNestedObjects[Module]().toSeq def millOuterCtx: Ctx = outerCtx0 def millSourcePath: os.Path = millOuterCtx.millSourcePath / (millOuterCtx.segment match { case Segment.Label(s) => Seq(s) @@ -85,15 +85,15 @@ object Module { // For some reason, this fails to pick up concrete `object`s nested directly within // another top-level concrete `object`. This is fine for now, since Mill's Ammonite // script/REPL runner always wraps user code in a wrapper object/trait - def reflectNestedObjects[T: ClassTag]: Array[T] = { - (reflectAll[T] ++ + def reflectNestedObjects[T: ClassTag](filter: String => Boolean = Function.const(true)): Array[T] = { + reflect[T](filter) ++ outer .getClass .getClasses .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) .flatMap(c => c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T]) - )).distinct + ).distinct } } } diff --git a/main/src/mill/main/ResolveSegments.scala b/main/src/mill/main/ResolveSegments.scala index ef8527d0aa1..dc3468bb222 100644 --- a/main/src/mill/main/ResolveSegments.scala +++ b/main/src/mill/main/ResolveSegments.scala @@ -57,8 +57,8 @@ object ResolveSegments extends Resolve[Segments] { val module = obj.millInternal - .reflectNestedObjects[Module] - .find(_.millOuterCtx.segment == Segment.Label(last)) + .reflectNestedObjects[Module](_ == last) + .headOption .map(m => Right(m.millModuleSegments)) command orElse target orElse module match { diff --git a/runner/src/mill/runner/MillBuildBootstrap.scala b/runner/src/mill/runner/MillBuildBootstrap.scala index 537640388d9..30ce29347ba 100644 --- a/runner/src/mill/runner/MillBuildBootstrap.scala +++ b/runner/src/mill/runner/MillBuildBootstrap.scala @@ -93,8 +93,8 @@ class MillBuildBootstrap( } val childRootModules: Seq[RootModule] = rootModule0 - .millModuleDirectChildren - .collect { case b: RootModule => b } + .millInternal + .reflectNestedObjects[RootModule]() val rootModuleOrErr = childRootModules match { case Seq() => Right(rootModule0) From 5cc0e359447d6a19a8b61e83e96445cdfc8c6f81 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 23 Apr 2023 13:23:49 +0800 Subject: [PATCH 02/57] flesh out ModuleInitErrorTests --- .../failure/module-init-error/repo/build.sc | 12 +++-- .../test/src/ModuleInitErrorTests.scala | 47 ++++++++++++------- main/core/src/mill/define/Module.scala | 2 +- main/src/mill/main/Resolve.scala | 7 ++- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/integration/failure/module-init-error/repo/build.sc b/integration/failure/module-init-error/repo/build.sc index 37a58bc1706..b65d4b0d76d 100644 --- a/integration/failure/module-init-error/repo/build.sc +++ b/integration/failure/module-init-error/repo/build.sc @@ -1,17 +1,21 @@ import mill._, scalalib._ -def rootTarget = T{ println("rooty"); "rooty2" } +def rootTarget = T{ println("Running rootTarget"); "rootTarget" } +def rootCommand(s: String) = T.command{ println(s"Running rootCommand $s") } object foo extends Module{ - def fooTarget = T{ 123 } + def fooTarget = T{ println(s"Running fooTarget"); 123 } + def fooCommand(s: String) = T.command{ println(s"Running fooCommand $s") } throw new Exception("Foo Boom") } object bar extends Module { - def barTarget = T { "abc" } + def barTarget = T { println(s"Running barTarget"); "abc" } + def barCommand(s: String) = T.command{ println(s"Running barCommand $s") } object Qux extends Module{ - def quxTarget = T { "xyz" } + def quxTarget = T { println(s"Running quxTarget"); "xyz" } + def quxCommand(s: String) = T.command{ println(s"Running quxCommand $s") } throw new Exception("Qux Boom") } } diff --git a/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala b/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala index d0cb6b8e423..eed93e592ee 100644 --- a/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala +++ b/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala @@ -8,32 +8,47 @@ object ModuleInitErrorTests extends IntegrationTestSuite { val tests = Tests { initWorkspace() - test("root") { + test("rootTarget") { // If we specify a target in the root module, we are not // affected by the sub-modules failing to initialize val res = evalStdout("rootTarget") - assert(res.isSuccess == true) - assert(res.out.contains("""rooty""")) - - val res2 = evalStdout("show", "rootTarget") - - assert(res2.isSuccess == true) - assert(res2.out.contains(""""rooty2"""")) + assert(res.out.contains("""Running rootTarget""")) + } + test("rootCommand") { + // If we specify a target in the root module, we are not + // affected by the sub-modules failing to initialize + val res = evalStdout("rootCommand", "hello") + assert(res.isSuccess == true) + assert(res.out.contains("""Running rootCommand hello""")) } - test("foo") { - println("foo 1") + test("fooTarget") { val res = evalStdout("foo.fooTarget") - println("foo 2") - assert(res.isSuccess == false) - println("foo 3") assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Foo Boom""")) - println("foo 4") } - test("barQux") { + test("fooCommand") { + val res = evalStdout("foo.fooCommand", "hello") + assert(res.isSuccess == false) + assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Foo Boom""")) + } + test("barTarget") { + val res = evalStdout("bar.barTarget") + assert(res.isSuccess == true) + assert(res.out.contains("""Running barTarget""")) + } + test("barCommand") { + val res = evalStdout("bar.barCommand", "hello") + assert(res.isSuccess == true) + assert(res.out.contains("""Running barCommand hello""")) + } + test("quxTarget") { val res = evalStdout("bar.qux.quxTarget") - + assert(res.isSuccess == false) + assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Qux Boom""")) + } + test("quxCommand") { + val res = evalStdout("bar.qux.quxCommand", "hello") assert(res.isSuccess == false) assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Qux Boom""")) } diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 713668ca70f..e8d6d9a7382 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -64,7 +64,7 @@ object Module { lazy val millModuleEnclosing: String = outer.millOuterCtx.enclosing lazy val millModuleLine: Int = outer.millOuterCtx.lineNum - private def reflect[T: ClassTag](filter: (String) => Boolean): Array[T] = { + private def reflect[T: ClassTag](filter: String => Boolean): Array[T] = { val runtimeCls = implicitly[ClassTag[T]].runtimeClass for { m <- outer.getClass.getMethods.sortBy(_.getName) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 7185f14a949..0e217150337 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -144,8 +144,7 @@ object Resolve { rest: Seq[String] ): Array[Option[Either[String, Command[_]]]] = { for { - child <- obj.millModuleDirectChildren - if child.millOuterCtx.segment == last + child <- obj.millInternal.reflectNestedObjects[Module](_ == last.pathSegments.last) res <- child match { case taskMod: TaskModule => Some( @@ -215,8 +214,8 @@ abstract class Resolve[R: ClassTag] { case "_" => obj.millModuleDirectChildren case _ => obj - .millModuleDirectChildren - .find(_.millOuterCtx.segment == Segment.Label(singleLabel)) + .millInternal.reflectNestedObjects[Module](_ == singleLabel) + .headOption .toSeq }, singleLabel match { From 9794eafc6ccf143094f57193e14c45db879cd2e3 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 23 Apr 2023 13:25:20 +0800 Subject: [PATCH 03/57] fix --- integration/failure/module-init-error/repo/build.sc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/failure/module-init-error/repo/build.sc b/integration/failure/module-init-error/repo/build.sc index b65d4b0d76d..3ed72aceb2a 100644 --- a/integration/failure/module-init-error/repo/build.sc +++ b/integration/failure/module-init-error/repo/build.sc @@ -13,7 +13,7 @@ object bar extends Module { def barTarget = T { println(s"Running barTarget"); "abc" } def barCommand(s: String) = T.command{ println(s"Running barCommand $s") } - object Qux extends Module{ + object qux extends Module{ def quxTarget = T { println(s"Running quxTarget"); "xyz" } def quxCommand(s: String) = T.command{ println(s"Running quxCommand $s") } throw new Exception("Qux Boom") From f06baede591fcf2ee663117209a3c1b9051b1277 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 23 Apr 2023 13:44:21 +0800 Subject: [PATCH 04/57] wip try to collapse Resolve into one impl --- .../contrib/scoverage/ScoverageReport.scala | 2 - main/src/mill/main/MainModule.scala | 83 +++-- main/src/mill/main/Resolve.scala | 283 ------------------ main/src/mill/main/ResolveMetadata.scala | 81 ----- main/src/mill/main/ResolveSegments.scala | 75 ----- main/src/mill/main/ResolveTasks.scala | 276 ++++++++++++++++- main/src/mill/main/RunScript.scala | 18 +- main/src/mill/main/TokenReaders.scala | 1 - 8 files changed, 315 insertions(+), 504 deletions(-) delete mode 100644 main/src/mill/main/Resolve.scala delete mode 100644 main/src/mill/main/ResolveMetadata.scala delete mode 100644 main/src/mill/main/ResolveSegments.scala diff --git a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala index 173f890f6bd..2c0f5e29bb0 100644 --- a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala +++ b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala @@ -82,7 +82,6 @@ trait ScoverageReport extends Module { dataTargets: String ): Task[PathRef] = { val sourcesTasks: Seq[Task[Seq[PathRef]]] = RunScript.resolveTasks( - mill.main.ResolveTasks, evaluator, Seq(sources), SelectMode.Single @@ -91,7 +90,6 @@ trait ScoverageReport extends Module { case Right(tasks) => tasks.asInstanceOf[Seq[Task[Seq[PathRef]]]] } val dataTasks: Seq[Task[PathRef]] = RunScript.resolveTasks( - mill.main.ResolveTasks, evaluator, Seq(dataTargets), SelectMode.Single diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 9b04af0912f..d292a2ed862 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -21,7 +21,7 @@ object MainModule { targets: Seq[String], selectMode: SelectMode )(f: List[NamedTask[Any]] => T): Result[T] = { - RunScript.resolveTasks(mill.main.ResolveTasks, evaluator, targets, selectMode) match { + RunScript.resolveTasks(evaluator, targets, selectMode) match { case Left(err) => Result.Failure(err) case Right(tasks) => Result.Success(f(tasks)) } @@ -74,19 +74,19 @@ trait MainModule extends mill.Module { * Resolves a mill query string and prints out the tasks it resolves to. */ def resolve(evaluator: Evaluator, targets: String*): Command[List[String]] = T.command { - val resolved: Either[String, List[String]] = RunScript.resolveTasks( - mill.main.ResolveMetadata, - evaluator, - targets, - SelectMode.Multi - ) - - resolved match { - case Left(err) => Result.Failure(err) - case Right(rs) => - rs.sorted.foreach(T.log.outputStream.println) - Result.Success(rs) - } +// val resolved: Either[String, List[String]] = RunScript.resolveTasks( +// evaluator, +// targets, +// SelectMode.Multi +// ) +// +// resolved match { +// case Left(err) => Result.Failure(err) +// case Right(rs) => +// rs.sorted.foreach(T.log.outputStream.println) +// Result.Success(rs) +// } + List.empty[String] } /** @@ -105,7 +105,6 @@ trait MainModule extends mill.Module { private def plan0(evaluator: Evaluator, targets: Seq[String]) = { RunScript.resolveTasks( - mill.main.ResolveTasks, evaluator, targets, SelectMode.Multi @@ -125,7 +124,6 @@ trait MainModule extends mill.Module { */ def path(evaluator: Evaluator, src: String, dest: String): Command[List[String]] = T.command { val resolved = RunScript.resolveTasks( - mill.main.ResolveTasks, evaluator, List(src, dest), SelectMode.Multi @@ -314,34 +312,34 @@ trait MainModule extends mill.Module { case _ => false } - val pathsToRemove = - if (targets.isEmpty) - Right(os.list(rootDir).filterNot(keepPath)) - else - RunScript.resolveTasks( - mill.main.ResolveSegments, - evaluator, - targets, - SelectMode.Multi - ).map { ts => - ts.flatMap { segments => - val evPpaths = EvaluatorPaths.resolveDestPaths(rootDir, segments) - val paths = Seq(evPpaths.dest, evPpaths.meta, evPpaths.log) - val potentialModulePath = rootDir / EvaluatorPaths.makeSegmentStrings(segments) - if (os.exists(potentialModulePath)) { - // this is either because of some pre-Mill-0.10 files lying around - // or most likely because the segments denote a module but not a task - // in which case we want to remove the module and all its sub-modules - // (If this logic is later found to be to harsh, we could further guard it, - // to when non of the other paths exists.) - paths :+ potentialModulePath - } else paths - } - } + val pathsToRemove = Right(os.list(rootDir).filterNot(keepPath)) +// if (targets.isEmpty) +// Right(os.list(rootDir).filterNot(keepPath)) +// else +// RunScript.resolveTasks( +// mill.main.ResolveSegments, +// evaluator, +// targets, +// SelectMode.Multi +// ).map { ts => +// ts.flatMap { segments => +// val evPpaths = EvaluatorPaths.resolveDestPaths(rootDir, segments) +// val paths = Seq(evPpaths.dest, evPpaths.meta, evPpaths.log) +// val potentialModulePath = rootDir / EvaluatorPaths.makeSegmentStrings(segments) +// if (os.exists(potentialModulePath)) { +// // this is either because of some pre-Mill-0.10 files lying around +// // or most likely because the segments denote a module but not a task +// // in which case we want to remove the module and all its sub-modules +// // (If this logic is later found to be to harsh, we could further guard it, +// // to when non of the other paths exists.) +// paths :+ potentialModulePath +// } else paths +// } +// } pathsToRemove match { - case Left(err) => - Result.Failure(err) +// case Left(err) => +// Result.Failure(err) case Right(paths) => val existing = paths.filter(p => os.exists(p)) T.log.debug(s"Cleaning ${existing.size} paths ...") @@ -411,7 +409,6 @@ trait MainModule extends mill.Module { } RunScript.resolveTasks( - mill.main.ResolveTasks, evaluator, targets, SelectMode.Multi diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala deleted file mode 100644 index 0e217150337..00000000000 --- a/main/src/mill/main/Resolve.scala +++ /dev/null @@ -1,283 +0,0 @@ -package mill.main - -import mill.define._ -import mill.define.TaskModule -import mainargs.{MainData, TokenGrouping} -import mill.main.ResolveMetadata.singleModuleMeta - -import scala.collection.immutable -import scala.reflect.ClassTag - -object Resolve { - - def unableToResolve(last: Segment, revSelectorsSoFar: Seq[Segment]): String = { - unableToResolve(Segments((last +: revSelectorsSoFar).reverse: _*).render) - } - - def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." - - def hintList(revSelectorsSoFar: Seq[Segment]) = { - val search = Segments(revSelectorsSoFar: _*).render - s" Try `mill resolve $search` to see what's available." - } - - def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Label("_")) - } - - def hintListCross(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Cross(Seq("__"))) - } - - def errorMsgBase[T]( - direct: Seq[T], - last0: T, - editSplit: String => String, - defaultErrorMsg: String - )(strings: T => Seq[String], render: T => String): Left[String, Nothing] = { - val last = strings(last0) - val similar = - direct - .map(x => (x, strings(x))) - .filter(_._2.length == last.length) - .map { case (d, s) => - ( - d, - s.zip(last).map { case (a, b) => LevenshteinDistance.editDistance(editSplit(a), b) }.sum - ) - } - .filter(_._2 < 3) - .sortBy(_._2) - - if (similar.headOption.exists(_._1 == last0)) { - // Special case: if the most similar segment is the desired segment itself, - // this means we are trying to resolve a module where a task is present. - // Special case the error message to make it something meaningful - Left("Task " + last0 + " is not a module and has no children.") - } else { - - val hint = similar match { - case Nil => defaultErrorMsg - case items => " Did you mean " + render(items.head._1) + "?" - } - Left(unableToResolve(render(last0)) + hint) - } - } - - def errorMsgLabel( - direct: Seq[String], - remaining: Seq[Segment], - revSelectorsSoFar: Seq[Segment] - ) = { - errorMsgBase( - direct, - Segments(revSelectorsSoFar ++ remaining: _*).render, - _.split('.').last, - hintListLabel(revSelectorsSoFar) - )( - rendered => Seq(rendered.split('.').last), - x => x - ) - } - - def errorMsgCross( - crossKeys: Seq[Seq[String]], - last: Seq[String], - revSelectorsSoFar: Seq[Segment] - ) = { - errorMsgBase( - crossKeys, - last, - x => x, - hintListCross(revSelectorsSoFar) - )( - crossKeys => crossKeys, - crossKeys => Segments((Segment.Cross(crossKeys) +: revSelectorsSoFar).reverse: _*).render - ) - } - - def invokeCommand( - target: Module, - name: String, - discover: Discover[Module], - rest: Seq[String] - ): immutable.Iterable[Either[String, Command[_]]] = - for { - (cls, entryPoints) <- discover.value - if cls.isAssignableFrom(target.getClass) - ep <- entryPoints - if ep._2.name == name - } yield { - mainargs.TokenGrouping.groupArgs( - rest, - ep._2.argSigs0, - allowPositional = true, - allowRepeats = false, - allowLeftover = ep._2.leftoverArgSig.nonEmpty - ).flatMap { grouped => - mainargs.Invoker.invoke( - target, - ep._2.asInstanceOf[MainData[_, Any]], - grouped.asInstanceOf[TokenGrouping[Any]] - ) - } match { - case mainargs.Result.Success(v: Command[_]) => Right(v) - case f: mainargs.Result.Failure => - Left( - mainargs.Renderer.renderResult( - ep._2, - f, - totalWidth = 100, - printHelpOnError = true, - docsOnNewLine = false, - customName = None, - customDoc = None - ) - ) - } - } - - def runDefault( - obj: Module, - last: Segment, - discover: Discover[_], - rest: Seq[String] - ): Array[Option[Either[String, Command[_]]]] = { - for { - child <- obj.millInternal.reflectNestedObjects[Module](_ == last.pathSegments.last) - res <- child match { - case taskMod: TaskModule => - Some( - invokeCommand( - child, - taskMod.defaultCommandName(), - discover.asInstanceOf[Discover[Module]], - rest - ).headOption - ) - case _ => None - } - } yield res - }.toArray -} - -abstract class Resolve[R: ClassTag] { - def endResolveCross( - obj: Module, - last: List[String], - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[R]] - def endResolveLabel( - obj: Module, - last: String, - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[R]] - - def resolve( - remainingSelector: List[Segment], - obj: mill.Module, - discover: Discover[_], - rest: Seq[String], - remainingCrossSelectors: List[List[String]] - ): Either[String, Seq[R]] = { - - remainingSelector match { - case Segment.Cross(last) :: Nil => - endResolveCross(obj, last.map(_.toString).toList, discover, rest) - case Segment.Label(last) :: Nil => - endResolveLabel(obj, last, discover, rest) - - case head :: tail => - def recurse( - searchModules: Seq[Module], - resolveFailureMsg: => Left[String, Nothing] - ): Either[String, Seq[R]] = { - val matching = searchModules - .map(m => resolve(tail, m, discover, rest, remainingCrossSelectors)) - - matching match { - case Seq(Left(err)) => Left(err) - case items => - items.collect { case Right(v) => v } match { - case Nil => resolveFailureMsg - case values => Right(values.flatten) - } - } - } - head match { - case Segment.Label(singleLabel) => - recurse( - singleLabel match { - case "__" => obj.millInternal.modules - case "_" => obj.millModuleDirectChildren - case _ => - obj - .millInternal.reflectNestedObjects[Module](_ == singleLabel) - .headOption - .toSeq - }, - singleLabel match { - case "_" => - Left( - "Cannot resolve " + Segments( - (remainingSelector.reverse ++ obj.millModuleSegments.value).reverse: _* - ).render + - ". Try `mill resolve " + Segments( - (Segment.Label("_") +: obj.millModuleSegments.value).reverse: _* - ).render + "` to see what's available." - ) - case "__" => - Resolve.errorMsgLabel( - singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty), - remainingSelector, - obj.millModuleSegments.value - ) - case _ => - Resolve.errorMsgLabel( - singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty), - Seq(Segment.Label(singleLabel)), - obj.millModuleSegments.value - ) - } - ).map( - _.distinctBy { - case t: NamedTask[_] => t.ctx.segments - case t => t - } - ) - case Segment.Cross(cross) => - obj match { - case c: Cross[_] => - val searchModules: Seq[Module] = - if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v - else if (cross.contains("_")) { - for { - (segments, v) <- c.segmentsToModules.toList - if segments.length == cross.length - if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } - } yield v - } else c.segmentsToModules.get(cross.toList).toSeq - - recurse( - searchModules = searchModules, - resolveFailureMsg = - Resolve.errorMsgCross( - c.segmentsToModules.map(_._1.map(_.toString)).toList, - cross.map(_.toString), - obj.millModuleSegments.value - ) - ) - case _ => - Left( - Resolve.unableToResolve(Segment.Cross(cross.map(_.toString)), tail) + - Resolve.hintListLabel(tail) - ) - } - } - - case Nil => Left("Selector cannot be empty") - } - } -} diff --git a/main/src/mill/main/ResolveMetadata.scala b/main/src/mill/main/ResolveMetadata.scala deleted file mode 100644 index 1f55e57bd32..00000000000 --- a/main/src/mill/main/ResolveMetadata.scala +++ /dev/null @@ -1,81 +0,0 @@ -package mill.main - -import mill.define._ - -object ResolveMetadata extends Resolve[String] { - def singleModuleMeta(obj: Module, discover: Discover[_], isRootModule: Boolean): Seq[String] = { - val modules = obj.millModuleDirectChildren.map(_.millModuleSegments.render) - val targets = - obj - .millInternal - .reflectAll[NamedTask[_]] - .map(_.toString) - val commands = - for { - (cls, entryPoints) <- discover.value - if cls.isAssignableFrom(obj.getClass) - ep <- entryPoints - } yield - if (isRootModule) ep._2.name - else s"$obj.${ep._2.name}" - - modules ++ targets ++ commands - } - - def endResolveLabel( - obj: Module, - last: String, - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[String]] = { - def direct = singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty) - last match { - case "__" => - Right( - // Filter out our own module in - obj.millInternal.modules.flatMap(m => singleModuleMeta(m, discover, m == obj)) - ) - case "_" => Right(direct) - case _ => - direct.find(_.split('.').last == last) match { - case None => - Resolve.errorMsgLabel(direct, Seq(Segment.Label(last)), obj.millModuleSegments.value) - case Some(s) => Right(Seq(s)) - } - } - } - - def endResolveCross( - obj: Module, - last: List[String], - discover: Discover[_], - rest: Seq[String] - ): Either[String, List[String]] = { - obj match { - case c: Cross[_] => - last match { - case List("__") => Right(c.crossModules.map(_.toString)) - case items => - c.segmentsToModules - .toList - .filter(_._1.length == items.length) - .filter(_._1.zip(last).forall { case (a, b) => b == "_" || a.toString == b }) - .map(_._2.toString) match { - case Nil => - Resolve.errorMsgCross( - c.segmentsToModules.map(_._1).toList, - last, - obj.millModuleSegments.value - ) - case res => Right(res) - } - - } - case _ => - Left( - Resolve.unableToResolve(Segment.Cross(last), obj.millModuleSegments.value) + - Resolve.hintListLabel(obj.millModuleSegments.value) - ) - } - } -} diff --git a/main/src/mill/main/ResolveSegments.scala b/main/src/mill/main/ResolveSegments.scala deleted file mode 100644 index dc3468bb222..00000000000 --- a/main/src/mill/main/ResolveSegments.scala +++ /dev/null @@ -1,75 +0,0 @@ -package mill.main - -import mill.define._ -import mill.main.ResolveMetadata.singleModuleMeta - -object ResolveSegments extends Resolve[Segments] { - - override def endResolveCross( - obj: Module, - last: List[String], - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[Segments]] = { - obj match { - case c: Cross[_] => - last match { - case List("__") => Right(c.crossModules.map(_.millModuleSegments)) - case items => - c.segmentsToModules - .filter(_._1.length == items.length) - .filter(_._1.zip(last).forall { case (a, b) => b == "_" || a == b }) - .map(_._2.millModuleSegments) match { - case Nil => - Resolve.errorMsgCross( - c.segmentsToModules.map(_._1).toList, - last, - obj.millModuleSegments.value - ) - case res => Right(res.toSeq) - } - } - case _ => - Left( - Resolve.unableToResolve(Segment.Cross(last), obj.millModuleSegments.value) + - Resolve.hintListLabel(obj.millModuleSegments.value) - ) - } - } - - def endResolveLabel( - obj: Module, - last: String, - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[Segments]] = { - val target = - obj - .millInternal - .reflectSingle[Target[_]](last) - .map(t => Right(t.ctx.segments)) - - val command = - Resolve - .invokeCommand(obj, last, discover.asInstanceOf[Discover[Module]], rest) - .headOption - .map(_.map(_.ctx.segments)) - - val module = - obj.millInternal - .reflectNestedObjects[Module](_ == last) - .headOption - .map(m => Right(m.millModuleSegments)) - - command orElse target orElse module match { - case None => - Resolve.errorMsgLabel( - singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty), - Seq(Segment.Label(last)), - obj.millModuleSegments.value - ) - - case Some(either) => either.right.map(Seq(_)) - } - } -} diff --git a/main/src/mill/main/ResolveTasks.scala b/main/src/mill/main/ResolveTasks.scala index 28b4a2e88ca..00663a02cb3 100644 --- a/main/src/mill/main/ResolveTasks.scala +++ b/main/src/mill/main/ResolveTasks.scala @@ -1,10 +1,268 @@ package mill.main +import mainargs.{MainData, TokenGrouping} import mill.define._ -import mill.main.ResolveMetadata.singleModuleMeta -object ResolveTasks extends Resolve[NamedTask[Any]] { +import scala.collection.immutable +//import mill.main.ResolveMetadata.singleModuleMeta +object ResolveTasks { + + def unableToResolve(last: Segment, revSelectorsSoFar: Seq[Segment]): String = { + unableToResolve(Segments((last +: revSelectorsSoFar).reverse: _*).render) + } + + def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." + + def hintList(revSelectorsSoFar: Seq[Segment]) = { + val search = Segments(revSelectorsSoFar: _*).render + s" Try `mill resolve $search` to see what's available." + } + + def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { + hintList(revSelectorsSoFar :+ Segment.Label("_")) + } + + def hintListCross(revSelectorsSoFar: Seq[Segment]) = { + hintList(revSelectorsSoFar :+ Segment.Cross(Seq("__"))) + } + + def errorMsgBase[T]( + direct: Seq[T], + last0: T, + editSplit: String => String, + defaultErrorMsg: String + )(strings: T => Seq[String], render: T => String): Left[String, Nothing] = { + val last = strings(last0) + val similar = + direct + .map(x => (x, strings(x))) + .filter(_._2.length == last.length) + .map { case (d, s) => + ( + d, + s.zip(last).map { case (a, b) => LevenshteinDistance.editDistance(editSplit(a), b) }.sum + ) + } + .filter(_._2 < 3) + .sortBy(_._2) + + if (similar.headOption.exists(_._1 == last0)) { + // Special case: if the most similar segment is the desired segment itself, + // this means we are trying to resolve a module where a task is present. + // Special case the error message to make it something meaningful + Left("Task " + last0 + " is not a module and has no children.") + } else { + + val hint = similar match { + case Nil => defaultErrorMsg + case items => " Did you mean " + render(items.head._1) + "?" + } + Left(unableToResolve(render(last0)) + hint) + } + } + + def errorMsgLabel( + direct: Seq[String], + remaining: Seq[Segment], + revSelectorsSoFar: Seq[Segment] + ) = { + errorMsgBase( + direct, + Segments(revSelectorsSoFar ++ remaining: _*).render, + _.split('.').last, + hintListLabel(revSelectorsSoFar) + )( + rendered => Seq(rendered.split('.').last), + x => x + ) + } + + def errorMsgCross( + crossKeys: Seq[Seq[String]], + last: Seq[String], + revSelectorsSoFar: Seq[Segment] + ) = { + errorMsgBase( + crossKeys, + last, + x => x, + hintListCross(revSelectorsSoFar) + )( + crossKeys => crossKeys, + crossKeys => Segments((Segment.Cross(crossKeys) +: revSelectorsSoFar).reverse: _*).render + ) + } + + def invokeCommand( + target: Module, + name: String, + discover: Discover[Module], + rest: Seq[String] + ): immutable.Iterable[Either[String, Command[_]]] = + for { + (cls, entryPoints) <- discover.value + if cls.isAssignableFrom(target.getClass) + ep <- entryPoints + if ep._2.name == name + } yield { + mainargs.TokenGrouping.groupArgs( + rest, + ep._2.argSigs0, + allowPositional = true, + allowRepeats = false, + allowLeftover = ep._2.leftoverArgSig.nonEmpty + ).flatMap { grouped => + mainargs.Invoker.invoke( + target, + ep._2.asInstanceOf[MainData[_, Any]], + grouped.asInstanceOf[TokenGrouping[Any]] + ) + } match { + case mainargs.Result.Success(v: Command[_]) => Right(v) + case f: mainargs.Result.Failure => + Left( + mainargs.Renderer.renderResult( + ep._2, + f, + totalWidth = 100, + printHelpOnError = true, + docsOnNewLine = false, + customName = None, + customDoc = None + ) + ) + } + } + + def runDefault( + obj: Module, + last: Segment, + discover: Discover[_], + rest: Seq[String] + ): Array[Option[Either[String, Command[_]]]] = { + for { + child <- obj.millInternal.reflectNestedObjects[Module](_ == last.pathSegments.last) + res <- child match { + case taskMod: TaskModule => + Some( + invokeCommand( + child, + taskMod.defaultCommandName(), + discover.asInstanceOf[Discover[Module]], + rest + ).headOption + ) + case _ => None + } + } yield res + }.toArray + def resolve( + remainingSelector: List[Segment], + obj: mill.Module, + discover: Discover[_], + rest: Seq[String], + remainingCrossSelectors: List[List[String]] + ): Either[String, Seq[NamedTask[Any]]] = { + + remainingSelector match { + case Segment.Cross(last) :: Nil => + endResolveCross(obj, last.map(_.toString).toList, discover, rest) + case Segment.Label(last) :: Nil => + endResolveLabel(obj, last, discover, rest) + + case head :: tail => + def recurse( + searchModules: Seq[Module], + resolveFailureMsg: => Left[String, Nothing] + ): Either[String, Seq[NamedTask[Any]]] = { + val matching = searchModules + .map(m => resolve(tail, m, discover, rest, remainingCrossSelectors)) + + matching match { + case Seq(Left(err)) => Left(err) + case items => + items.collect { case Right(v) => v } match { + case Nil => resolveFailureMsg + case values => Right(values.flatten) + } + } + } + + head match { + case Segment.Label(singleLabel) => + recurse( + singleLabel match { + case "__" => obj.millInternal.modules + case "_" => obj.millModuleDirectChildren + case _ => + obj + .millInternal.reflectNestedObjects[Module](_ == singleLabel) + .headOption + .toSeq + }, + singleLabel match { + case "_" => + Left( + "Cannot resolve " + Segments( + (remainingSelector.reverse ++ obj.millModuleSegments.value).reverse: _* + ).render + + ". Try `mill resolve " + Segments( + (Segment.Label("_") +: obj.millModuleSegments.value).reverse: _* + ).render + "` to see what's available." + ) + case "__" => + errorMsgLabel( + sys.error("singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty)"), + remainingSelector, + obj.millModuleSegments.value + ) + case _ => + errorMsgLabel( + sys.error("singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty)"), + Seq(Segment.Label(singleLabel)), + obj.millModuleSegments.value + ) + } + ).map( + _.distinctBy { + case t: NamedTask[_] => t.ctx.segments + case t => t + } + ) + case Segment.Cross(cross) => + obj match { + case c: Cross[_] => + val searchModules: Seq[Module] = + if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v + else if (cross.contains("_")) { + for { + (segments, v) <- c.segmentsToModules.toList + if segments.length == cross.length + if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } + } yield v + } else c.segmentsToModules.get(cross.toList).toSeq + + recurse( + searchModules = searchModules, + resolveFailureMsg = + errorMsgCross( + c.segmentsToModules.map(_._1.map(_.toString)).toList, + cross.map(_.toString), + obj.millModuleSegments.value + ) + ) + case _ => + Left( + unableToResolve(Segment.Cross(cross.map(_.toString)), tail) + + hintListLabel(tail) + ) + } + } + + case Nil => Left("Selector cannot be empty") + } + } def endResolveCross( obj: Module, last: List[String], @@ -13,7 +271,7 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { ): Either[String, Seq[NamedTask[Any]]] = { obj match { case _: Cross[Module] => - Resolve.runDefault(obj, Segment.Cross(last), discover, rest).flatten.headOption match { + runDefault(obj, Segment.Cross(last), discover, rest).flatten.headOption match { case None => Left( "Cannot find default task to evaluate for module " + @@ -23,8 +281,8 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { } case _ => Left( - Resolve.unableToResolve(Segment.Cross(last), obj.millModuleSegments.value) + - Resolve.hintListLabel(obj.millModuleSegments.value) + unableToResolve(Segment.Cross(last), obj.millModuleSegments.value) + + hintListLabel(obj.millModuleSegments.value) ) } } @@ -50,7 +308,7 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { .reflectSingle[NamedTask[_]](last) .map(Right(_)) - val command = Resolve.invokeCommand( + val command = invokeCommand( obj, last, discover.asInstanceOf[Discover[Module]], @@ -60,7 +318,7 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { command .orElse(target) .orElse { - Resolve.runDefault( + runDefault( obj, Segment.Label(last), discover, @@ -68,8 +326,8 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { ).flatten.headOption } match { case None => - Resolve.errorMsgLabel( - singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty), + errorMsgLabel( + sys.error("singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty)"), Seq(Segment.Label(last)), obj.millModuleSegments.value ) diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index 8addfd4e670..51679d1338b 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -19,27 +19,25 @@ object RunScript { type TaskName = String - def resolveTasks[T, R: ClassTag]( - resolver: mill.main.Resolve[R], + def resolveTasks[T]( evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode - ): Either[String, List[R]] = { + ): Either[String, List[NamedTask[Any]]] = { val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) val resolvedGroups = parsedGroups.flatMap { groups => val resolved = groups.map { parsed: TargetsWithParams => - resolveTasks(resolver, evaluator, Right(parsed)) + resolveTasks(evaluator, Right(parsed)) } EitherOps.sequence(resolved) } resolvedGroups.map(_.flatten.toList) } - private def resolveTasks[T, R: ClassTag]( - resolver: mill.main.Resolve[R], + private def resolveTasks[T]( evaluator: Evaluator, targetsWithParams: Either[String, TargetsWithParams] - ): Either[String, List[R]] = { + ): Either[String, List[NamedTask[Any]]] = { for { parsed <- targetsWithParams (selectors, args) = parsed @@ -56,7 +54,7 @@ object RunScript { // main build. Resolving targets from external builds as CLI arguments // is not currently supported mill.eval.Evaluator.currentEvaluator.set(evaluator) - resolver.resolve( + ResolveTasks.resolve( sel.value.toList, rootModule, rootModule.millDiscover, @@ -113,7 +111,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[ujson.Value])]])] = { - for (targets <- resolveTasks(mill.main.ResolveTasks, evaluator, scriptArgs, selectMode)) + for (targets <- resolveTasks(evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluate(evaluator, Agg.from(targets.distinct)) @@ -132,7 +130,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[(TaskName, ujson.Value)])]])] = { - for (targets <- resolveTasks(mill.main.ResolveTasks, evaluator, scriptArgs, selectMode)) + for (targets <- resolveTasks(evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluateNamed(evaluator, Agg.from(targets.distinct)) diff --git a/main/src/mill/main/TokenReaders.scala b/main/src/mill/main/TokenReaders.scala index 5248060e5a1..4721c5651fc 100644 --- a/main/src/mill/main/TokenReaders.scala +++ b/main/src/mill/main/TokenReaders.scala @@ -12,7 +12,6 @@ object Tasks { shortName = "", read = s => RunScript.resolveTasks( - mill.main.ResolveTasks, Evaluator.currentEvaluator.get, s, SelectMode.Single From 00f01782e64f54add9dfd6f8b04302a21d9076af Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 24 Apr 2023 06:33:56 +0800 Subject: [PATCH 05/57] ResolveTasks compiles again with new implementation, incomplete --- main/core/src/mill/define/Module.scala | 53 +++- main/src/mill/main/Resolve.scala | 180 +++++++++++++ main/src/mill/main/ResolveTasks.scala | 340 ------------------------ main/src/mill/main/RunScript.scala | 3 +- main/test/src/mill/main/MainTests.scala | 7 +- 5 files changed, 221 insertions(+), 362 deletions(-) create mode 100644 main/src/mill/main/Resolve.scala delete mode 100644 main/src/mill/main/ResolveTasks.scala diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index e8d6d9a7382..b9d8e8c7e41 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -42,6 +42,39 @@ class Module(implicit outerCtx0: mill.define.Ctx) extends mill.moduledefs.Cacher } object Module { + def reflect( + outer: Class[_], + inner: Class[_], + filter: String => Boolean + ): Seq[java.lang.reflect.Method] = { + for { + m <- outer.getMethods.sortBy(_.getName) + n = decode(m.getName) + if filter(n) && + ParseArgs.isLegalIdentifier(n) && + m.getParameterCount == 0 && + (m.getModifiers & Modifier.STATIC) == 0 && + (m.getModifiers & Modifier.ABSTRACT) == 0 && + inner.isAssignableFrom(m.getReturnType) + } yield m + } + + // For some reason, this fails to pick up concrete `object`s nested directly within + // another top-level concrete `object`. This is fine for now, since Mill's Ammonite + // script/REPL runner always wraps user code in a wrapper object/trait + def reflectNestedObjects[T: ClassTag]( + outer: Class[_], + filter: String => Boolean = Function.const(true) + ): Seq[java.lang.reflect.Member] = { + reflect(outer, classOf[Object], filter) ++ + outer + .getClasses + .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) + .flatMap(c => + c.getFields.find(_.getName == "MODULE$") + ).distinct + } + class Internal(outer: Module) { def traverse[T](f: Module => Seq[T]): Seq[T] = { def rec(m: Module): Seq[T] = f(m) ++ m.millModuleDirectChildren.flatMap(rec) @@ -64,28 +97,20 @@ object Module { lazy val millModuleEnclosing: String = outer.millOuterCtx.enclosing lazy val millModuleLine: Int = outer.millOuterCtx.lineNum - private def reflect[T: ClassTag](filter: String => Boolean): Array[T] = { - val runtimeCls = implicitly[ClassTag[T]].runtimeClass - for { - m <- outer.getClass.getMethods.sortBy(_.getName) - n = decode(m.getName) - if filter(n) && - ParseArgs.isLegalIdentifier(n) && - m.getParameterCount == 0 && - (m.getModifiers & Modifier.STATIC) == 0 && - (m.getModifiers & Modifier.ABSTRACT) == 0 && - runtimeCls.isAssignableFrom(m.getReturnType) - } yield m.invoke(outer).asInstanceOf[T] + def reflect[T: ClassTag](filter: String => Boolean): Seq[T] = { + Module.reflect(outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter) + .map(_.invoke(outer).asInstanceOf[T]) } - def reflectAll[T: ClassTag]: Array[T] = reflect(Function.const(true)) + def reflectAll[T: ClassTag]: Seq[T] = reflect[T](Function.const(true)) def reflectSingle[T: ClassTag](label: String): Option[T] = reflect(_ == label).headOption // For some reason, this fails to pick up concrete `object`s nested directly within // another top-level concrete `object`. This is fine for now, since Mill's Ammonite // script/REPL runner always wraps user code in a wrapper object/trait - def reflectNestedObjects[T: ClassTag](filter: String => Boolean = Function.const(true)): Array[T] = { + def reflectNestedObjects[T: ClassTag](filter: String => Boolean = Function.const(true)) + : Seq[T] = { reflect[T](filter) ++ outer .getClass diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala new file mode 100644 index 00000000000..c2ec8b55f56 --- /dev/null +++ b/main/src/mill/main/Resolve.scala @@ -0,0 +1,180 @@ +package mill.main + +import mainargs.{MainData, TokenGrouping} +import mill.define._ +import mill.util.EitherOps + +import scala.collection.immutable + +object ResolveTasks{ + def resolve( + remainingSelector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ): Either[String, Seq[NamedTask[Any]]] = { + val raw = Resolve.resolve(remainingSelector, Resolve.Resolved.Module(current), discover, args) + .map(_.collect { + case t: Resolve.Resolved.Target => Right(t.value) + case t: Resolve.Resolved.Command => t.value() + }) + + val cooked = raw.map(EitherOps.sequence(_)) + + cooked.flatten + } +} +/** + * Takes a single list of segments, without braces but including wildcards, and + * resolves all possible modules, targets or commands that the segments could + * resolve to. + * + * Does not report errors if no possible resolved values are found; such + * reporting is left to the caller. + */ +object Resolve { + sealed trait Resolved + object Resolved { + case class Module(value: mill.define.Module) extends Resolved + case class Target(value: mill.define.Target[_]) extends Resolved + case class Command(name: String, value: () => Either[String, mill.define.Command[_]]) + extends Resolved + } + + def resolve( + remainingSelector: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String] + ): Either[String, Seq[Resolved]] = remainingSelector match { + case Nil => Right(Seq(current)) + + case head :: tail => + def recurse(searchModules: Seq[Either[String, Resolved]]): Either[String, Seq[Resolved]] = { + val (errors, successes) = searchModules + .map(_.flatMap(resolve(tail, _, discover, args))) + .partitionMap(identity) + + if (errors.nonEmpty) Left(errors.mkString("\n")) + else Right(successes.flatten) + } + + (head, current) match { + case (Segment.Label(singleLabel), Resolved.Module(obj)) => + recurse( + singleLabel match { + case "__" => + obj + .millInternal + .modules + .flatMap(m => + Seq(Right(Resolved.Module(m))) ++ + resolveDirectChildren(m, None, discover, args) + ) + case "_" => resolveDirectChildren(obj, None, discover, args) + case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args) + } + ).map( + _.distinctBy { + case t: NamedTask[_] => t.ctx.segments + case t => t + } + ) + + case (Segment.Cross(cross), Resolved.Module(c: Cross[_])) => + val searchModules: Seq[Module] = + if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v + else if (cross.contains("_")) { + for { + (segments, v) <- c.segmentsToModules.toList + if segments.length == cross.length + if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } + } yield v + } else c.segmentsToModules.get(cross.toList).toSeq + + recurse(searchModules.map(m => Right(Resolved.Module(m)))) + + case _ => Right(Nil) + } + } + + def resolveDirectChildren( + obj: Module, + nameOpt: Option[String] = None, + discover: Discover[_], + args: Seq[String] + ): Seq[Either[String, Resolved]] = { + + def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n) + + val modules = obj + .millInternal + .reflectNestedObjects[Module](namePred) + .map(t => Right(Resolved.Module(t))) + + val targets = obj + .millInternal + .reflect[Target[_]](namePred) + .map(t => Right(Resolved.Target(t))) + + val commands = Module + .reflect(obj.getClass, classOf[Command[_]], namePred) + .map(_.getName) + .map(name => + Right(Resolved.Command( + name, + () => + invokeCommand( + obj, + name, + discover.asInstanceOf[Discover[Module]], + args + ).head + )) + ) + + modules ++ targets ++ commands + } + + def invokeCommand( + target: Module, + name: String, + discover: Discover[Module], + rest: Seq[String] + ): immutable.Iterable[Either[String, Command[_]]] = + for { + (cls, entryPoints) <- discover.value + if cls.isAssignableFrom(target.getClass) + ep <- entryPoints + if ep._2.name == name + } yield { + mainargs.TokenGrouping.groupArgs( + rest, + ep._2.argSigs0, + allowPositional = true, + allowRepeats = false, + allowLeftover = ep._2.leftoverArgSig.nonEmpty + ).flatMap { grouped => + mainargs.Invoker.invoke( + target, + ep._2.asInstanceOf[MainData[_, Any]], + grouped.asInstanceOf[TokenGrouping[Any]] + ) + } match { + case mainargs.Result.Success(v: Command[_]) => Right(v) + case f: mainargs.Result.Failure => + Left( + mainargs.Renderer.renderResult( + ep._2, + f, + totalWidth = 100, + printHelpOnError = true, + docsOnNewLine = false, + customName = None, + customDoc = None + ) + ) + } + } + +} diff --git a/main/src/mill/main/ResolveTasks.scala b/main/src/mill/main/ResolveTasks.scala deleted file mode 100644 index 00663a02cb3..00000000000 --- a/main/src/mill/main/ResolveTasks.scala +++ /dev/null @@ -1,340 +0,0 @@ -package mill.main - -import mainargs.{MainData, TokenGrouping} -import mill.define._ - -import scala.collection.immutable -//import mill.main.ResolveMetadata.singleModuleMeta - -object ResolveTasks { - - def unableToResolve(last: Segment, revSelectorsSoFar: Seq[Segment]): String = { - unableToResolve(Segments((last +: revSelectorsSoFar).reverse: _*).render) - } - - def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." - - def hintList(revSelectorsSoFar: Seq[Segment]) = { - val search = Segments(revSelectorsSoFar: _*).render - s" Try `mill resolve $search` to see what's available." - } - - def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Label("_")) - } - - def hintListCross(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Cross(Seq("__"))) - } - - def errorMsgBase[T]( - direct: Seq[T], - last0: T, - editSplit: String => String, - defaultErrorMsg: String - )(strings: T => Seq[String], render: T => String): Left[String, Nothing] = { - val last = strings(last0) - val similar = - direct - .map(x => (x, strings(x))) - .filter(_._2.length == last.length) - .map { case (d, s) => - ( - d, - s.zip(last).map { case (a, b) => LevenshteinDistance.editDistance(editSplit(a), b) }.sum - ) - } - .filter(_._2 < 3) - .sortBy(_._2) - - if (similar.headOption.exists(_._1 == last0)) { - // Special case: if the most similar segment is the desired segment itself, - // this means we are trying to resolve a module where a task is present. - // Special case the error message to make it something meaningful - Left("Task " + last0 + " is not a module and has no children.") - } else { - - val hint = similar match { - case Nil => defaultErrorMsg - case items => " Did you mean " + render(items.head._1) + "?" - } - Left(unableToResolve(render(last0)) + hint) - } - } - - def errorMsgLabel( - direct: Seq[String], - remaining: Seq[Segment], - revSelectorsSoFar: Seq[Segment] - ) = { - errorMsgBase( - direct, - Segments(revSelectorsSoFar ++ remaining: _*).render, - _.split('.').last, - hintListLabel(revSelectorsSoFar) - )( - rendered => Seq(rendered.split('.').last), - x => x - ) - } - - def errorMsgCross( - crossKeys: Seq[Seq[String]], - last: Seq[String], - revSelectorsSoFar: Seq[Segment] - ) = { - errorMsgBase( - crossKeys, - last, - x => x, - hintListCross(revSelectorsSoFar) - )( - crossKeys => crossKeys, - crossKeys => Segments((Segment.Cross(crossKeys) +: revSelectorsSoFar).reverse: _*).render - ) - } - - def invokeCommand( - target: Module, - name: String, - discover: Discover[Module], - rest: Seq[String] - ): immutable.Iterable[Either[String, Command[_]]] = - for { - (cls, entryPoints) <- discover.value - if cls.isAssignableFrom(target.getClass) - ep <- entryPoints - if ep._2.name == name - } yield { - mainargs.TokenGrouping.groupArgs( - rest, - ep._2.argSigs0, - allowPositional = true, - allowRepeats = false, - allowLeftover = ep._2.leftoverArgSig.nonEmpty - ).flatMap { grouped => - mainargs.Invoker.invoke( - target, - ep._2.asInstanceOf[MainData[_, Any]], - grouped.asInstanceOf[TokenGrouping[Any]] - ) - } match { - case mainargs.Result.Success(v: Command[_]) => Right(v) - case f: mainargs.Result.Failure => - Left( - mainargs.Renderer.renderResult( - ep._2, - f, - totalWidth = 100, - printHelpOnError = true, - docsOnNewLine = false, - customName = None, - customDoc = None - ) - ) - } - } - - def runDefault( - obj: Module, - last: Segment, - discover: Discover[_], - rest: Seq[String] - ): Array[Option[Either[String, Command[_]]]] = { - for { - child <- obj.millInternal.reflectNestedObjects[Module](_ == last.pathSegments.last) - res <- child match { - case taskMod: TaskModule => - Some( - invokeCommand( - child, - taskMod.defaultCommandName(), - discover.asInstanceOf[Discover[Module]], - rest - ).headOption - ) - case _ => None - } - } yield res - }.toArray - def resolve( - remainingSelector: List[Segment], - obj: mill.Module, - discover: Discover[_], - rest: Seq[String], - remainingCrossSelectors: List[List[String]] - ): Either[String, Seq[NamedTask[Any]]] = { - - remainingSelector match { - case Segment.Cross(last) :: Nil => - endResolveCross(obj, last.map(_.toString).toList, discover, rest) - case Segment.Label(last) :: Nil => - endResolveLabel(obj, last, discover, rest) - - case head :: tail => - def recurse( - searchModules: Seq[Module], - resolveFailureMsg: => Left[String, Nothing] - ): Either[String, Seq[NamedTask[Any]]] = { - val matching = searchModules - .map(m => resolve(tail, m, discover, rest, remainingCrossSelectors)) - - matching match { - case Seq(Left(err)) => Left(err) - case items => - items.collect { case Right(v) => v } match { - case Nil => resolveFailureMsg - case values => Right(values.flatten) - } - } - } - - head match { - case Segment.Label(singleLabel) => - recurse( - singleLabel match { - case "__" => obj.millInternal.modules - case "_" => obj.millModuleDirectChildren - case _ => - obj - .millInternal.reflectNestedObjects[Module](_ == singleLabel) - .headOption - .toSeq - }, - singleLabel match { - case "_" => - Left( - "Cannot resolve " + Segments( - (remainingSelector.reverse ++ obj.millModuleSegments.value).reverse: _* - ).render + - ". Try `mill resolve " + Segments( - (Segment.Label("_") +: obj.millModuleSegments.value).reverse: _* - ).render + "` to see what's available." - ) - case "__" => - errorMsgLabel( - sys.error("singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty)"), - remainingSelector, - obj.millModuleSegments.value - ) - case _ => - errorMsgLabel( - sys.error("singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty)"), - Seq(Segment.Label(singleLabel)), - obj.millModuleSegments.value - ) - } - ).map( - _.distinctBy { - case t: NamedTask[_] => t.ctx.segments - case t => t - } - ) - case Segment.Cross(cross) => - obj match { - case c: Cross[_] => - val searchModules: Seq[Module] = - if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v - else if (cross.contains("_")) { - for { - (segments, v) <- c.segmentsToModules.toList - if segments.length == cross.length - if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } - } yield v - } else c.segmentsToModules.get(cross.toList).toSeq - - recurse( - searchModules = searchModules, - resolveFailureMsg = - errorMsgCross( - c.segmentsToModules.map(_._1.map(_.toString)).toList, - cross.map(_.toString), - obj.millModuleSegments.value - ) - ) - case _ => - Left( - unableToResolve(Segment.Cross(cross.map(_.toString)), tail) + - hintListLabel(tail) - ) - } - } - - case Nil => Left("Selector cannot be empty") - } - } - def endResolveCross( - obj: Module, - last: List[String], - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[NamedTask[Any]]] = { - obj match { - case _: Cross[Module] => - runDefault(obj, Segment.Cross(last), discover, rest).flatten.headOption match { - case None => - Left( - "Cannot find default task to evaluate for module " + - Segments((obj.millModuleSegments.value :+ Segment.Cross(last)): _*).render - ) - case Some(v) => v.map(Seq(_)) - } - case _ => - Left( - unableToResolve(Segment.Cross(last), obj.millModuleSegments.value) + - hintListLabel(obj.millModuleSegments.value) - ) - } - } - - def endResolveLabel( - obj: Module, - last: String, - discover: Discover[_], - rest: Seq[String] - ): Either[String, Seq[NamedTask[Any]]] = last match { - case "__" => - Right( - obj.millInternal.modules - .filter(_ != obj) - .flatMap(m => m.millInternal.reflectAll[NamedTask[_]]) - ) - case "_" => Right(obj.millInternal.reflectAll[NamedTask[_]]) - - case _ => - val target = - obj - .millInternal - .reflectSingle[NamedTask[_]](last) - .map(Right(_)) - - val command = invokeCommand( - obj, - last, - discover.asInstanceOf[Discover[Module]], - rest - ).headOption - - command - .orElse(target) - .orElse { - runDefault( - obj, - Segment.Label(last), - discover, - rest - ).flatten.headOption - } match { - case None => - errorMsgLabel( - sys.error("singleModuleMeta(obj, discover, obj.millModuleSegments.value.isEmpty)"), - Seq(Segment.Label(last)), - obj.millModuleSegments.value - ) - - // Contents of `either` *must* be a `Task`, because we only select - // methods returning `Task` in the discovery process - case Some(either) => either.map(Seq(_)) - } - } -} diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index 51679d1338b..7f9810228c9 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -58,8 +58,7 @@ object RunScript { sel.value.toList, rootModule, rootModule.millDiscover, - args, - crossSelectors.toList + args ) } finally { mill.eval.Evaluator.currentEvaluator.set(null) diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/MainTests.scala index 2fdf22ec688..4f66bec305f 100644 --- a/main/test/src/mill/main/MainTests.scala +++ b/main/test/src/mill/main/MainTests.scala @@ -18,16 +18,11 @@ object MainTests extends TestSuite { val expected = expected0.map(_.map(_(module))) val resolved = for { selectors <- mill.define.ParseArgs(selectorStrings, SelectMode.Single).map(_.head._1.head) - crossSelectors = selectors._2.value.map { - case Segment.Cross(x) => x.toList.map(_.toString) - case _ => Nil - } task <- mill.main.ResolveTasks.resolve( selectors._2.value.toList, module, module.millDiscover, - Nil, - crossSelectors.toList + Nil ) } yield task // doesn't work for commands, don't know why From 718be7277e86061516133d941233d63c4756d340 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 24 Apr 2023 08:46:24 +0800 Subject: [PATCH 06/57] try to re-introduce resolve error reporting --- main/src/mill/main/Resolve.scala | 236 ++++++++++++++++++++++++------- 1 file changed, 182 insertions(+), 54 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index c2ec8b55f56..01a25bace7a 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -13,7 +13,13 @@ object ResolveTasks{ discover: Discover[_], args: Seq[String] ): Either[String, Seq[NamedTask[Any]]] = { - val raw = Resolve.resolve(remainingSelector, Resolve.Resolved.Module(current), discover, args) + val raw = Resolve.resolve( + remainingSelector, + Resolve.Resolved.Module("", current), + discover, + args, + Nil + ) .map(_.collect { case t: Resolve.Resolved.Target => Right(t.value) case t: Resolve.Resolved.Command => t.value() @@ -29,38 +35,45 @@ object ResolveTasks{ * resolves all possible modules, targets or commands that the segments could * resolve to. * - * Does not report errors if no possible resolved values are found; such - * reporting is left to the caller. + * Reports an error if nothing is resolved. */ object Resolve { - sealed trait Resolved + sealed trait Resolved{ + def name: String + } object Resolved { - case class Module(value: mill.define.Module) extends Resolved - case class Target(value: mill.define.Target[_]) extends Resolved + case class Module(name: String, value: mill.define.Module) extends Resolved + case class Target(name: String, value: mill.define.Target[_]) extends Resolved case class Command(name: String, value: () => Either[String, mill.define.Command[_]]) extends Resolved } def resolve( - remainingSelector: List[Segment], - current: Resolved, - discover: Discover[_], - args: Seq[String] + remainingSelector: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String], + revSelectorsSoFar: List[Segment] ): Either[String, Seq[Resolved]] = remainingSelector match { case Nil => Right(Seq(current)) case head :: tail => - def recurse(searchModules: Seq[Either[String, Resolved]]): Either[String, Seq[Resolved]] = { + def recurse(searchModules: Seq[Either[String, Resolved]], + resolveFailureMsg: => Left[String, Nothing]): Either[String, Seq[Resolved]] = { val (errors, successes) = searchModules - .map(_.flatMap(resolve(tail, _, discover, args))) + .map(_.flatMap(resolve(tail, _, discover, args, head :: revSelectorsSoFar))) .partitionMap(identity) if (errors.nonEmpty) Left(errors.mkString("\n")) - else Right(successes.flatten) + else successes.flatten match{ + case Nil => resolveFailureMsg + case values => Right(values) + } } (head, current) match { - case (Segment.Label(singleLabel), Resolved.Module(obj)) => + case (Segment.Label(singleLabel), Resolved.Module(_, obj)) => + lazy val directChildren = resolveDirectChildren(obj, None, discover, args) recurse( singleLabel match { case "__" => @@ -68,11 +81,34 @@ object Resolve { .millInternal .modules .flatMap(m => - Seq(Right(Resolved.Module(m))) ++ + Seq(Right(Resolved.Module(m.millModuleSegments.parts.last, m))) ++ resolveDirectChildren(m, None, discover, args) ) - case "_" => resolveDirectChildren(obj, None, discover, args) + case "_" => directChildren case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args) + }, + singleLabel match { + case "_" => + Left( + "Cannot resolve " + Segments( + (remainingSelector.reverse ++ obj.millModuleSegments.value).reverse: _* + ).render + + ". Try `mill resolve " + Segments( + (Segment.Label("_") +: obj.millModuleSegments.value).reverse: _* + ).render + "` to see what's available." + ) + case "__" => + Resolve.errorMsgLabel( + directChildren.map(_.right.get.name), + remainingSelector, + obj.millModuleSegments.value + ) + case _ => + Resolve.errorMsgLabel( + directChildren.map(_.right.get.name), + Seq(Segment.Label(singleLabel)), + obj.millModuleSegments.value + ) } ).map( _.distinctBy { @@ -81,7 +117,7 @@ object Resolve { } ) - case (Segment.Cross(cross), Resolved.Module(c: Cross[_])) => + case (Segment.Cross(cross), Resolved.Module(_, c: Cross[_])) => val searchModules: Seq[Module] = if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v else if (cross.contains("_")) { @@ -92,7 +128,14 @@ object Resolve { } yield v } else c.segmentsToModules.get(cross.toList).toSeq - recurse(searchModules.map(m => Right(Resolved.Module(m)))) + recurse( + searchModules.map(m => Right(Resolved.Module("", m))), + Resolve.errorMsgCross( + c.segmentsToModules.map(_._1.map(_.toString)).toList, + cross.map(_.toString), + c.millModuleSegments.value + ) + ) case _ => Right(Nil) } @@ -110,12 +153,11 @@ object Resolve { val modules = obj .millInternal .reflectNestedObjects[Module](namePred) - .map(t => Right(Resolved.Module(t))) + .map(t => Right(Resolved.Module(t.millModuleSegments.parts.last, t))) - val targets = obj - .millInternal - .reflect[Target[_]](namePred) - .map(t => Right(Resolved.Target(t))) + val targets = Module + .reflect(obj.getClass, classOf[Target[_]], namePred) + .map(m => Right(Resolved.Target(m.getName, m.invoke(obj).asInstanceOf[Target[_]]))) val commands = Module .reflect(obj.getClass, classOf[Command[_]], namePred) @@ -141,40 +183,126 @@ object Resolve { name: String, discover: Discover[Module], rest: Seq[String] - ): immutable.Iterable[Either[String, Command[_]]] = - for { - (cls, entryPoints) <- discover.value - if cls.isAssignableFrom(target.getClass) - ep <- entryPoints - if ep._2.name == name - } yield { - mainargs.TokenGrouping.groupArgs( - rest, - ep._2.argSigs0, - allowPositional = true, - allowRepeats = false, - allowLeftover = ep._2.leftoverArgSig.nonEmpty - ).flatMap { grouped => - mainargs.Invoker.invoke( - target, - ep._2.asInstanceOf[MainData[_, Any]], - grouped.asInstanceOf[TokenGrouping[Any]] + ): immutable.Iterable[Either[String, Command[_]]] = for { + (cls, entryPoints) <- discover.value + if cls.isAssignableFrom(target.getClass) + ep <- entryPoints + if ep._2.name == name + } yield { + mainargs.TokenGrouping.groupArgs( + rest, + ep._2.argSigs0, + allowPositional = true, + allowRepeats = false, + allowLeftover = ep._2.leftoverArgSig.nonEmpty + ).flatMap { grouped => + mainargs.Invoker.invoke( + target, + ep._2.asInstanceOf[MainData[_, Any]], + grouped.asInstanceOf[TokenGrouping[Any]] + ) + } match { + case mainargs.Result.Success(v: Command[_]) => Right(v) + case f: mainargs.Result.Failure => + Left( + mainargs.Renderer.renderResult( + ep._2, + f, + totalWidth = 100, + printHelpOnError = true, + docsOnNewLine = false, + customName = None, + customDoc = None + ) ) - } match { - case mainargs.Result.Success(v: Command[_]) => Right(v) - case f: mainargs.Result.Failure => - Left( - mainargs.Renderer.renderResult( - ep._2, - f, - totalWidth = 100, - printHelpOnError = true, - docsOnNewLine = false, - customName = None, - customDoc = None - ) + } + } + + def unableToResolve(last: Segment, revSelectorsSoFar: Seq[Segment]): String = { + unableToResolve(Segments((last +: revSelectorsSoFar).reverse: _*).render) + } + + def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." + + + def hintList(revSelectorsSoFar: Seq[Segment]) = { + val search = Segments(revSelectorsSoFar: _*).render + s" Try `mill resolve $search` to see what's available." + } + + def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { + hintList(revSelectorsSoFar :+ Segment.Label("_")) + } + + def hintListCross(revSelectorsSoFar: Seq[Segment]) = { + hintList(revSelectorsSoFar :+ Segment.Cross(Seq("__"))) + } + + def errorMsgBase[T]( + direct: Seq[T], + last0: T, + editSplit: String => String, + defaultErrorMsg: String + )(strings: T => Seq[String], render: T => String): Left[String, Nothing] = { + val last = strings(last0) + val similar = + direct + .map(x => (x, strings(x))) + .filter(_._2.length == last.length) + .map { case (d, s) => + ( + d, + s.zip(last).map { case (a, b) => LevenshteinDistance.editDistance(editSplit(a), b) }.sum ) + } + .filter(_._2 < 3) + .sortBy(_._2) + + if (similar.headOption.exists(_._1 == last0)) { + // Special case: if the most similar segment is the desired segment itself, + // this means we are trying to resolve a module where a task is present. + // Special case the error message to make it something meaningful + Left("Task " + last0 + " is not a module and has no children.") + } else { + + val hint = similar match { + case Nil => defaultErrorMsg + case items => " Did you mean " + render(items.head._1) + "?" } + Left(unableToResolve(render(last0)) + hint) } + } + + def errorMsgLabel( + possibleMembers: Seq[String], + remaining: Seq[Segment], + revSelectorsSoFar: Seq[Segment] + ) = { + errorMsgBase( + possibleMembers, + Segments(revSelectorsSoFar ++ remaining: _*).render, + _.split('.').last, + hintListLabel(revSelectorsSoFar) + )( + rendered => Seq(rendered.split('.').last), + x => x + ) + } + + def errorMsgCross( + crossKeys: Seq[Seq[String]], + last: Seq[String], + revSelectorsSoFar: Seq[Segment] + ) = { + errorMsgBase( + crossKeys, + last, + x => x, + hintListCross(revSelectorsSoFar) + )( + crossKeys => crossKeys, + crossKeys => Segments((Segment.Cross(crossKeys) +: revSelectorsSoFar).reverse: _*).render + ) + } } From 092120d3189bbb3335fa6940f7ac128f25db9582 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 24 Apr 2023 08:56:08 +0800 Subject: [PATCH 07/57] wip --- main/src/mill/main/Resolve.scala | 48 ++++++++------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 01a25bace7a..f6352db5b54 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -87,29 +87,11 @@ object Resolve { case "_" => directChildren case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args) }, - singleLabel match { - case "_" => - Left( - "Cannot resolve " + Segments( - (remainingSelector.reverse ++ obj.millModuleSegments.value).reverse: _* - ).render + - ". Try `mill resolve " + Segments( - (Segment.Label("_") +: obj.millModuleSegments.value).reverse: _* - ).render + "` to see what's available." - ) - case "__" => - Resolve.errorMsgLabel( - directChildren.map(_.right.get.name), - remainingSelector, - obj.millModuleSegments.value - ) - case _ => - Resolve.errorMsgLabel( - directChildren.map(_.right.get.name), - Seq(Segment.Label(singleLabel)), - obj.millModuleSegments.value - ) - } + Resolve.errorMsgLabel( + directChildren.map(_.right.get.name), + remainingSelector, + revSelectorsSoFar + ) ).map( _.distinctBy { case t: NamedTask[_] => t.ctx.segments @@ -131,8 +113,8 @@ object Resolve { recurse( searchModules.map(m => Right(Resolved.Module("", m))), Resolve.errorMsgCross( - c.segmentsToModules.map(_._1.map(_.toString)).toList, - cross.map(_.toString), + c.segmentsToModules.keys.toList, + cross, c.millModuleSegments.value ) ) @@ -258,19 +240,13 @@ object Resolve { .filter(_._2 < 3) .sortBy(_._2) - if (similar.headOption.exists(_._1 == last0)) { - // Special case: if the most similar segment is the desired segment itself, - // this means we are trying to resolve a module where a task is present. - // Special case the error message to make it something meaningful - Left("Task " + last0 + " is not a module and has no children.") - } else { - val hint = similar match { - case Nil => defaultErrorMsg - case items => " Did you mean " + render(items.head._1) + "?" - } - Left(unableToResolve(render(last0)) + hint) + val hint = similar match { + case Nil => defaultErrorMsg + case items => " Did you mean " + render(items.head._1) + "?" } + Left(unableToResolve(render(last0)) + hint) + } def errorMsgLabel( From d5a1d2e0c03c691cf98aa32d69d23f4097fb5b43 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 24 Apr 2023 08:58:10 +0800 Subject: [PATCH 08/57] re-implement not-a-module error --- main/src/mill/main/Resolve.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index f6352db5b54..fdaac91eb94 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -72,6 +72,8 @@ object Resolve { } (head, current) match { + case (_, _: Resolved.Target | _: Resolved.Command) => + Left(s"Task ${current.name} is not a module and has no children.") case (Segment.Label(singleLabel), Resolved.Module(_, obj)) => lazy val directChildren = resolveDirectChildren(obj, None, discover, args) recurse( From dfc432887cbb21e4725f1c3e161d783e13a0f649 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 24 Apr 2023 17:18:39 +0800 Subject: [PATCH 09/57] wip --- main/core/src/mill/define/BaseModule.scala | 2 +- main/core/src/mill/define/Module.scala | 12 +- main/core/src/mill/define/ParseArgs.scala | 2 +- main/core/src/mill/define/Segment.scala | 12 +- main/core/src/mill/define/Segments.scala | 20 +- main/core/src/mill/eval/Evaluator.scala | 4 +- main/src/mill/main/Resolve.scala | 273 ++++++++++-------- main/src/mill/main/RunScript.scala | 2 +- main/test/src/mill/main/MainTests.scala | 132 ++++----- main/test/src/mill/util/ParseArgsTest.scala | 4 +- .../src/mill/scalalib/UnresolvedPath.scala | 4 +- 11 files changed, 251 insertions(+), 216 deletions(-) diff --git a/main/core/src/mill/define/BaseModule.scala b/main/core/src/mill/define/BaseModule.scala index 69f8960b1fb..b68268d38bb 100644 --- a/main/core/src/mill/define/BaseModule.scala +++ b/main/core/src/mill/define/BaseModule.scala @@ -57,6 +57,6 @@ abstract class ExternalModule(implicit "External modules must be at a top-level static path, not " + millModuleEnclosing0.value ) override implicit def millModuleSegments = { - Segments(millModuleEnclosing0.value.split('.').map(Segment.Label).toIndexedSeq: _*) + Segments(millModuleEnclosing0.value.split('.').map(Segment.Label).toIndexedSeq) } } diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index b9d8e8c7e41..02fbabd2576 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -116,9 +116,15 @@ object Module { .getClass .getClasses .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) - .flatMap(c => - c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T]) - ).distinct + .flatMap { c => + c.getSimpleName match{ + case s"$name$$" if filter(name)=> + c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T]) + case _ => None + } + + } + .distinct } } } diff --git a/main/core/src/mill/define/ParseArgs.scala b/main/core/src/mill/define/ParseArgs.scala index b59575b714f..846a8cf923a 100644 --- a/main/core/src/mill/define/ParseArgs.scala +++ b/main/core/src/mill/define/ParseArgs.scala @@ -185,7 +185,7 @@ object ParseArgs { def segment[_p: P] = P(ident).map(Segment.Label) def crossSegment[_p: P] = P("[" ~ ident2.rep(1, sep = ",") ~ "]").map(Segment.Cross) def simpleQuery[_p: P] = P(segment ~ ("." ~ segment | crossSegment).rep).map { - case (h, rest) => Segments(h, rest: _*) + case (h, rest) => Segments(h +: rest) } def query[_p: P] = P(simpleQuery ~ ("/" ~/ simpleQuery).?).map { case (q, None) => (None, q) diff --git a/main/core/src/mill/define/Segment.scala b/main/core/src/mill/define/Segment.scala index 1d3f0aaa781..9477feab47a 100644 --- a/main/core/src/mill/define/Segment.scala +++ b/main/core/src/mill/define/Segment.scala @@ -3,11 +3,17 @@ package mill.define sealed trait Segment { def pathSegments: Seq[String] = this match { case Segment.Label(s) => Seq(s) - case Segment.Cross(vs) => vs.map(_.toString) + case Segment.Cross(vs) => vs } + + def render: String } object Segment { - final case class Label(value: String) extends Segment - final case class Cross(value: Seq[String]) extends Segment + final case class Label(value: String) extends Segment{ + def render = s".$value" + } + final case class Cross(value: Seq[String]) extends Segment{ + def render = "[" + value.mkString(",") + "]" + } } diff --git a/main/core/src/mill/define/Segments.scala b/main/core/src/mill/define/Segments.scala index c70b79eba3f..2580770e8cf 100644 --- a/main/core/src/mill/define/Segments.scala +++ b/main/core/src/mill/define/Segments.scala @@ -7,10 +7,10 @@ package mill.define * `.`-separated segments are [[Segment.Label]]s, * while `[]`-delimited segments are [[Segment.Cross]]s */ -case class Segments private (value: Segment*) { +case class Segments private (value: Seq[Segment]) { - def ++(other: Seq[Segment]): Segments = Segments(value ++ other: _*) - def ++(other: Segments): Segments = Segments(value ++ other.value: _*) + def ++(other: Seq[Segment]): Segments = Segments(value ++ other) + def ++(other: Segments): Segments = Segments(value ++ other.value) def parts: List[String] = value.toList match { case Nil => Nil @@ -33,10 +33,7 @@ case class Segments private (value: Segment*) { def render: String = value.toList match { case Nil => "" case Segment.Label(head) :: rest => - val stringSegments = rest.map { - case Segment.Label(s) => "." + s - case Segment.Cross(vs) => "[" + vs.mkString(",") + "]" - } + val stringSegments = rest.map(_.render) head + stringSegments.mkString case Segment.Cross(_) :: _ => throw new IllegalArgumentException("Segments must start with a Label, but found a Cross.") @@ -44,10 +41,7 @@ case class Segments private (value: Segment*) { } object Segments { - def apply(): Segments = new Segments() - def apply(head: Segment.Label, tail: Segment*): Segments = new Segments(head +: tail: _*) - - def labels(values: String*): Segments = - Segments(values.map(Segment.Label): _*) - + def apply(): Segments = new Segments(Nil) + def apply(items: List[Segment]): Segments = new Segments(items) + def labels(values: String*): Segments = Segments(values.map(Segment.Label)) } diff --git a/main/core/src/mill/eval/Evaluator.scala b/main/core/src/mill/eval/Evaluator.scala index 1e843e58e78..887b3e7a73f 100644 --- a/main/core/src/mill/eval/Evaluator.scala +++ b/main/core/src/mill/eval/Evaluator.scala @@ -822,8 +822,8 @@ object Evaluator { val Segment.Label(tName) = segments.value.last Segments( segments.value.init ++ - Seq(Segment.Label(tName + ".super")) ++ - t.ctx.enclosing.split("[.# ]").map(Segment.Label): _* + Seq(Segment.Label(tName + ".super")) ++ + t.ctx.enclosing.split("[.# ]").map(Segment.Label) ) } ) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index fdaac91eb94..099ea5a64cb 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -6,30 +6,6 @@ import mill.util.EitherOps import scala.collection.immutable -object ResolveTasks{ - def resolve( - remainingSelector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String] - ): Either[String, Seq[NamedTask[Any]]] = { - val raw = Resolve.resolve( - remainingSelector, - Resolve.Resolved.Module("", current), - discover, - args, - Nil - ) - .map(_.collect { - case t: Resolve.Resolved.Target => Right(t.value) - case t: Resolve.Resolved.Command => t.value() - }) - - val cooked = raw.map(EitherOps.sequence(_)) - - cooked.flatten - } -} /** * Takes a single list of segments, without braces but including wildcards, and * resolves all possible modules, targets or commands that the segments could @@ -38,9 +14,55 @@ object ResolveTasks{ * Reports an error if nothing is resolved. */ object Resolve { + def resolveTasks(remainingSelector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String]): Either[String, Set[NamedTask[Any]]] = { + Resolve.resolve( + remainingSelector, + Resolve.Resolved.Module("", current), + discover, + args, + Nil + ) match { + case Resolve.Success(value) => + println("S") + EitherOps.sequence( + value.collect { + case t: Resolve.Resolved.Target => Right(t.value) + case t: Resolve.Resolved.Command => t.value() + } + ).map(_.toSet[NamedTask[Any]]) + + case Resolve.NotFound(segments, found, next, possibleNexts) => + val errorMsg = found.head match{ + case s: Resolved.Module => + next match { + case Segment.Label(s) => + val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } + pprint.log(possibleStrings) + pprint.log(segments.render) + + errorMsgLabel(s, possibleStrings, segments.value.toList) + case Segment.Cross(keys) => + errorMsgCross(keys, possibleNexts.collect { case Segment.Cross(keys) => keys }.toSeq, segments.value.toList) + } + case _ => + + unableToResolve((segments ++ Seq(next)).render) + + " Task " + segments.render + " is not a module and has no children." + } + + Left(errorMsg) + + case Resolve.Error(value) => Left(value) + } + } + sealed trait Resolved{ def name: String } + object Resolved { case class Module(name: String, value: mill.define.Module) extends Resolved case class Target(name: String, value: mill.define.Target[_]) extends Resolved @@ -48,58 +70,74 @@ object Resolve { extends Resolved } + sealed trait Result + case class Success(value: Set[Resolved]) extends Result + sealed trait Failed extends Result + case class NotFound(deepest: Segments, + found: Set[Resolved], + next: Segment, + possibleNexts: Set[Segment]) extends Failed + case class Error(msg: String) extends Failed + + + def resolve( remainingSelector: List[Segment], current: Resolved, discover: Discover[_], args: Seq[String], - revSelectorsSoFar: List[Segment] - ): Either[String, Seq[Resolved]] = remainingSelector match { - case Nil => Right(Seq(current)) - + revSelectorsSoFar0: List[Segment] + ): Result = remainingSelector match { + case Nil => + Success(Set(current)) case head :: tail => - def recurse(searchModules: Seq[Either[String, Resolved]], - resolveFailureMsg: => Left[String, Nothing]): Either[String, Seq[Resolved]] = { - val (errors, successes) = searchModules - .map(_.flatMap(resolve(tail, _, discover, args, head :: revSelectorsSoFar))) - .partitionMap(identity) - - if (errors.nonEmpty) Left(errors.mkString("\n")) - else successes.flatten match{ - case Nil => resolveFailureMsg - case values => Right(values) + val revSelectorsSoFar = head :: revSelectorsSoFar0 + def recurse(searchModules: Set[Resolved]): Result = { + val (failures, successesLists) = searchModules + .map(resolve(tail, _, discover, args, revSelectorsSoFar)) + .partitionMap{case s: Success => Right(s.value); case f: Failed => Left(f)} + + val (errors, notFounds) = failures.partitionMap{ + case s: NotFound => Right(s) + case s: Error => Left(s.msg) + } + if (errors.nonEmpty) Error(errors.mkString("\n")) + else { + val successes = successesLists.flatten + if (successes.nonEmpty) Success(successes) + else { + notFounds.headOption.getOrElse { + val mod = current.asInstanceOf[Resolved.Module].value + val directChildren: Set[Segment] = resolveDirectChildren(mod, None, discover, args) + .map(e => Segment.Label(e.right.get.name)) + pprint.log(mod) + pprint.log(directChildren) + NotFound(Segments(revSelectorsSoFar0.reverse), Set(current), head, directChildren) + } + } } } (head, current) match { - case (_, _: Resolved.Target | _: Resolved.Command) => - Left(s"Task ${current.name} is not a module and has no children.") case (Segment.Label(singleLabel), Resolved.Module(_, obj)) => lazy val directChildren = resolveDirectChildren(obj, None, discover, args) - recurse( + EitherOps.sequence( singleLabel match { case "__" => obj .millInternal .modules .flatMap(m => - Seq(Right(Resolved.Module(m.millModuleSegments.parts.last, m))) ++ + Seq(Right(Resolved.Module(m.millModuleSegments.parts.lastOption.getOrElse(""), m))) ++ resolveDirectChildren(m, None, discover, args) ) case "_" => directChildren case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args) - }, - Resolve.errorMsgLabel( - directChildren.map(_.right.get.name), - remainingSelector, - revSelectorsSoFar - ) - ).map( - _.distinctBy { - case t: NamedTask[_] => t.ctx.segments - case t => t } - ) + ) match{ + case Left(err) => Error(err) + case Right(v) => recurse(v.toSet) + } case (Segment.Cross(cross), Resolved.Module(_, c: Cross[_])) => val searchModules: Seq[Module] = @@ -112,26 +150,33 @@ object Resolve { } yield v } else c.segmentsToModules.get(cross.toList).toSeq - recurse( - searchModules.map(m => Right(Resolved.Module("", m))), - Resolve.errorMsgCross( - c.segmentsToModules.keys.toList, - cross, - c.millModuleSegments.value - ) - ) + recurse(searchModules.map(m => Resolved.Module("", m)).toSet) + + case _ => + NotFound( + Segments(revSelectorsSoFar0.reverse), + Set(current), + head, + current match{ + case Resolved.Module(_, c: Cross[_]) => + c.segmentsToModules.keys.toSet.map(Segment.Cross) + + case Resolved.Module(_, obj) => + resolveDirectChildren(obj, None, discover, args).map(e => Segment.Label(e.right.get.name)) - case _ => Right(Nil) + case _ => Set() + } + ) } } + def resolveDirectChildren( obj: Module, nameOpt: Option[String] = None, discover: Discover[_], args: Seq[String] - ): Seq[Either[String, Resolved]] = { - + ): Set[Either[String, Resolved]] = { def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n) val modules = obj @@ -159,7 +204,7 @@ object Resolve { )) ) - modules ++ targets ++ commands + (modules ++ targets ++ commands).toSet } def invokeCommand( @@ -202,15 +247,11 @@ object Resolve { } } - def unableToResolve(last: Segment, revSelectorsSoFar: Seq[Segment]): String = { - unableToResolve(Segments((last +: revSelectorsSoFar).reverse: _*).render) - } - def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." def hintList(revSelectorsSoFar: Seq[Segment]) = { - val search = Segments(revSelectorsSoFar: _*).render + val search = Segments(revSelectorsSoFar).render s" Try `mill resolve $search` to see what's available." } @@ -222,65 +263,53 @@ object Resolve { hintList(revSelectorsSoFar :+ Segment.Cross(Seq("__"))) } - def errorMsgBase[T]( - direct: Seq[T], - last0: T, - editSplit: String => String, - defaultErrorMsg: String - )(strings: T => Seq[String], render: T => String): Left[String, Nothing] = { - val last = strings(last0) - val similar = - direct - .map(x => (x, strings(x))) - .filter(_._2.length == last.length) - .map { case (d, s) => - ( - d, - s.zip(last).map { case (a, b) => LevenshteinDistance.editDistance(editSplit(a), b) }.sum - ) - } - .filter(_._2 < 3) - .sortBy(_._2) + def findMostSimilar(given: String, + options: Set[String]): Option[String] = { + options + .map { option => (option, LevenshteinDistance.editDistance(given, option))} + .filter(_._2 < 3) + .minByOption(_._2) + .map(_._1) + } + def errorMsgLabel(given: String, + possibleMembers: Set[String], + revSelectorsSoFar: List[Segment]) = { + def render(x: String) = { + val rendered = Segments((Segment.Label(x) :: revSelectorsSoFar).reverse).render + pprint.log(x) + pprint.log(revSelectorsSoFar) + pprint.log(rendered) + rendered + } - val hint = similar match { - case Nil => defaultErrorMsg - case items => " Did you mean " + render(items.head._1) + "?" + val suggestion = findMostSimilar(given, possibleMembers) match { + case None => hintListLabel(revSelectorsSoFar) + case Some(similar) => " Did you mean " + render(similar) + "?" } - Left(unableToResolve(render(last0)) + hint) - } + val msg = unableToResolve(render(given)) + suggestion - def errorMsgLabel( - possibleMembers: Seq[String], - remaining: Seq[Segment], - revSelectorsSoFar: Seq[Segment] - ) = { - errorMsgBase( - possibleMembers, - Segments(revSelectorsSoFar ++ remaining: _*).render, - _.split('.').last, - hintListLabel(revSelectorsSoFar) - )( - rendered => Seq(rendered.split('.').last), - x => x - ) + msg } - def errorMsgCross( - crossKeys: Seq[Seq[String]], - last: Seq[String], - revSelectorsSoFar: Seq[Segment] - ) = { - errorMsgBase( - crossKeys, - last, - x => x, - hintListCross(revSelectorsSoFar) - )( - crossKeys => crossKeys, - crossKeys => Segments((Segment.Cross(crossKeys) +: revSelectorsSoFar).reverse: _*).render - ) + def errorMsgCross(givenKeys: Seq[String], + possibleCrossKeys: Seq[Seq[String]], + revSelectorsSoFar: List[Segment]) = { + + def render(xs: Seq[String]) = { + Segments(Segment.Cross(xs) :: revSelectorsSoFar).render + } + + val similarKeysOpts = givenKeys + .zip(possibleCrossKeys) + .map{case (givenKey, possibleKeys) => findMostSimilar(givenKey, possibleKeys.toSet)} + + val suggestion = + if (similarKeysOpts.exists(_.isEmpty)) hintListCross(revSelectorsSoFar) + else " Did you mean " + render(similarKeysOpts.map{case Some(v) => v}) + "?" + + unableToResolve(render(givenKeys)) + suggestion } } diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index 7f9810228c9..471afe97f35 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -54,7 +54,7 @@ object RunScript { // main build. Resolving targets from external builds as CLI arguments // is not currently supported mill.eval.Evaluator.currentEvaluator.set(evaluator) - ResolveTasks.resolve( + Resolve.resolveTasks( sel.value.toList, rootModule, rootModule.millDiscover, diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/MainTests.scala index 4f66bec305f..a3a4245d15d 100644 --- a/main/test/src/mill/main/MainTests.scala +++ b/main/test/src/mill/main/MainTests.scala @@ -7,18 +7,18 @@ object MainTests extends TestSuite { def check[T <: mill.define.BaseModule](module: T)( selectorString: String, - expected0: Either[String, Seq[T => NamedTask[_]]] + expected0: Either[String, Set[T => NamedTask[_]]] ) = checkSeq(module)(Seq(selectorString), expected0) def checkSeq[T <: mill.define.BaseModule](module: T)( selectorStrings: Seq[String], - expected0: Either[String, Seq[T => NamedTask[_]]] + expected0: Either[String, Set[T => NamedTask[_]]] ) = { val expected = expected0.map(_.map(_(module))) val resolved = for { selectors <- mill.define.ParseArgs(selectorStrings, SelectMode.Single).map(_.head._1.head) - task <- mill.main.ResolveTasks.resolve( + task <- mill.main.Resolve.resolveTasks( selectors._2.value.toList, module, module.millDiscover, @@ -35,7 +35,7 @@ object MainTests extends TestSuite { import graphs._ "single" - { val check = MainTests.check(singleton) _ - "pos" - check("single", Right(Seq(_.single))) + "pos" - check("single", Right(Set(_.single))) "neg1" - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) "neg2" - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) "neg3" - check("nsiigle", Left("Cannot resolve nsiigle. Did you mean single?")) @@ -47,47 +47,47 @@ object MainTests extends TestSuite { "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") ) - "neg6" - check("single.doesntExist", Left("Task single is not a module and has no children.")) +// "neg6" - check("single.doesntExist", Left("Task single is not a module and has no children.")) "neg7" - check("", Left("Selector cannot be empty")) } - "backtickIdentifiers" - { - val check = MainTests.check(bactickIdentifiers) _ - "pos1" - check("up-target", Right(Seq(_.`up-target`))) - "pos2" - check("a-down-target", Right(Seq(_.`a-down-target`))) - "neg1" - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) - "neg2" - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) - "neg3" - check( - "up-target.doesntExist", - Left("Task up-target is not a module and has no children.") - ) - "neg4" - check("", Left("Selector cannot be empty")) - "neg5" - check( - "invisible&", - Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.") - ) - "nested" - { - "pos" - check("nested-module.nested-target", Right(Seq(_.`nested-module`.`nested-target`))) - "neg" - check( - "nested-module.doesntExist", - Left( - "Cannot resolve nested-module.doesntExist. Try `mill resolve nested-module._` to see what's available." - ) - ) - } - } +// "backtickIdentifiers" - { +// val check = MainTests.check(bactickIdentifiers) _ +// "pos1" - check("up-target", Right(Seq(_.`up-target`))) +// "pos2" - check("a-down-target", Right(Seq(_.`a-down-target`))) +// "neg1" - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) +// "neg2" - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) +// "neg3" - check( +// "up-target.doesntExist", +// Left("Task up-target is not a module and has no children.") +// ) +// "neg4" - check("", Left("Selector cannot be empty")) +// "neg5" - check( +// "invisible&", +// Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.") +// ) +// "nested" - { +// "pos" - check("nested-module.nested-target", Right(Seq(_.`nested-module`.`nested-target`))) +// "neg" - check( +// "nested-module.doesntExist", +// Left( +// "Cannot resolve nested-module.doesntExist. Try `mill resolve nested-module._` to see what's available." +// ) +// ) +// } +// } "nested" - { val check = MainTests.check(nestedModule) _ - "pos1" - check("single", Right(Seq(_.single))) - "pos2" - check("nested.single", Right(Seq(_.nested.single))) - "pos3" - check("classInstance.single", Right(Seq(_.classInstance.single))) + "pos1" - check("single", Right(Set(_.single))) + "pos2" - check("nested.single", Right(Set(_.nested.single))) + "pos3" - check("classInstance.single", Right(Set(_.classInstance.single))) "neg1" - check( "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") ) - "neg2" - check( - "single.doesntExist", - Left("Task single is not a module and has no children.") - ) +// "neg2" - check( +// "single.doesntExist", +// Left("Task single is not a module and has no children.") +// ) "neg3" - check( "nested.doesntExist", Left( @@ -106,26 +106,26 @@ object MainTests extends TestSuite { ) "wildcard" - check( "_.single", - Right(Seq( + Right(Set( _.classInstance.single, _.nested.single )) ) "wildcardNeg" - check( "_._.single", - Left("Cannot resolve _._.single. Try `mill resolve _` to see what's available.") + Left("Cannot resolve _._.single. Task _._ is not a module and has no children.") ) "wildcardNeg2" - check( "_._.__", - Left("Cannot resolve _._.__. Try `mill resolve _` to see what's available.") + Left("Cannot resolve _._.__. Task _._ is not a module and has no children.") ) "wildcardNeg3" - check( "nested._.foobar", - Left("Cannot resolve nested._.foobar. Try `mill resolve nested._` to see what's available.") + Left("Cannot resolve nested._.foobar. Task nested._ is not a module and has no children.") ) "wildcard2" - check( "__.single", - Right(Seq( + Right(Set( _.single, _.classInstance.single, _.nested.single @@ -134,7 +134,7 @@ object MainTests extends TestSuite { "wildcard3" - check( "_.__.single", - Right(Seq( + Right(Set( _.classInstance.single, _.nested.single )) @@ -144,8 +144,8 @@ object MainTests extends TestSuite { "cross" - { "single" - { val check = MainTests.check(singleCross) _ - "pos1" - check("cross[210].suffix", Right(Seq(_.cross("210").suffix))) - "pos2" - check("cross[211].suffix", Right(Seq(_.cross("211").suffix))) + "pos1" - check("cross[210].suffix", Right(Set(_.cross("210").suffix))) + "pos2" - check("cross[211].suffix", Right(Set(_.cross("211").suffix))) "neg1" - check( "cross[210].doesntExist", Left( @@ -170,7 +170,7 @@ object MainTests extends TestSuite { ) "wildcard" - check( "cross[_].suffix", - Right(Seq( + Right(Set( _.cross("210").suffix, _.cross("211").suffix, _.cross("212").suffix @@ -178,7 +178,7 @@ object MainTests extends TestSuite { ) "wildcard2" - check( "cross[__].suffix", - Right(Seq( + Right(Set( _.cross("210").suffix, _.cross("211").suffix, _.cross("212").suffix @@ -189,11 +189,11 @@ object MainTests extends TestSuite { val check = MainTests.check(doubleCross) _ "pos1" - check( "cross[210,jvm].suffix", - Right(Seq(_.cross("210", "jvm").suffix)) + Right(Set(_.cross("210", "jvm").suffix)) ) "pos2" - check( "cross[211,jvm].suffix", - Right(Seq(_.cross("211", "jvm").suffix)) + Right(Set(_.cross("211", "jvm").suffix)) ) "wildcard" - { "labelNeg1" - check( @@ -224,7 +224,7 @@ object MainTests extends TestSuite { ) "labelPos" - check( "__.suffix", - Right(Seq( + Right(Set( _.cross("210", "jvm").suffix, _.cross("210", "js").suffix, _.cross("211", "jvm").suffix, @@ -236,7 +236,7 @@ object MainTests extends TestSuite { ) "first" - check( "cross[_,jvm].suffix", - Right(Seq( + Right(Set( _.cross("210", "jvm").suffix, _.cross("211", "jvm").suffix, _.cross("212", "jvm").suffix @@ -244,14 +244,14 @@ object MainTests extends TestSuite { ) "second" - check( "cross[210,_].suffix", - Right(Seq( + Right(Set( _.cross("210", "jvm").suffix, _.cross("210", "js").suffix )) ) "both" - check( "cross[_,_].suffix", - Right(Seq( + Right(Set( _.cross("210", "jvm").suffix, _.cross("210", "js").suffix, _.cross("211", "jvm").suffix, @@ -263,7 +263,7 @@ object MainTests extends TestSuite { ) "both2" - check( "cross[__].suffix", - Right(Seq( + Right(Set( _.cross("210", "jvm").suffix, _.cross("210", "js").suffix, _.cross("211", "jvm").suffix, @@ -279,11 +279,11 @@ object MainTests extends TestSuite { val check = MainTests.check(nestedCrosses) _ "pos1" - check( "cross[210].cross2[js].suffix", - Right(Seq(_.cross("210").cross2("js").suffix)) + Right(Set(_.cross("210").cross2("js").suffix)) ) "pos2" - check( "cross[211].cross2[jvm].suffix", - Right(Seq(_.cross("211").cross2("jvm").suffix)) + Right(Set(_.cross("211").cross2("jvm").suffix)) ) "pos2NoDefaultTask" - check( "cross[211].cross2[jvm]", @@ -292,7 +292,7 @@ object MainTests extends TestSuite { "wildcard" - { "first" - check( "cross[_].cross2[jvm].suffix", - Right(Seq( + Right(Set( _.cross("210").cross2("jvm").suffix, _.cross("211").cross2("jvm").suffix, _.cross("212").cross2("jvm").suffix @@ -300,7 +300,7 @@ object MainTests extends TestSuite { ) "second" - check( "cross[210].cross2[_].suffix", - Right(Seq( + Right(Set( _.cross("210").cross2("jvm").suffix, _.cross("210").cross2("js").suffix, _.cross("210").cross2("native").suffix @@ -308,7 +308,7 @@ object MainTests extends TestSuite { ) "both" - check( "cross[_].cross2[_].suffix", - Right(Seq( + Right(Set( _.cross("210").cross2("jvm").suffix, _.cross("210").cross2("js").suffix, _.cross("210").cross2("native").suffix, @@ -327,29 +327,29 @@ object MainTests extends TestSuite { val check = MainTests.checkSeq(nestedTaskCrosses) _ "pos1" - check( Seq("cross1[210].cross2[js].suffixCmd"), - Right(Seq(_.cross1("210").cross2("js").suffixCmd())) + Right(Set(_.cross1("210").cross2("js").suffixCmd())) ) "pos1Default" - check( Seq("cross1[210].cross2[js]"), - Right(Seq(_.cross1("210").cross2("js").suffixCmd())) + Right(Set(_.cross1("210").cross2("js").suffixCmd())) ) // does not work because we're reflecting the Module for `def` without args, // which misses command with args :-( // "pos1WithWildcard" - check( -// Seq("cross1[210].cross2[js]._"), -// Right(Seq(_.cross1("210").cross2("js").suffixCmd())) +// Set("cross1[210].cross2[js]._"), +// Right(Set(_.cross1("210").cross2("js").suffixCmd())) // ) "pos1WithArgs" - check( Seq("cross1[210].cross2[js].suffixCmd", "suffix-arg"), - Right(Seq(_.cross1("210").cross2("js").suffixCmd("suffix-arg"))) + Right(Set(_.cross1("210").cross2("js").suffixCmd("suffix-arg"))) ) "pos2" - check( Seq("cross1[211].cross2[jvm].suffixCmd"), - Right(Seq(_.cross1("211").cross2("jvm").suffixCmd())) + Right(Set(_.cross1("211").cross2("jvm").suffixCmd())) ) "pos2Default" - check( Seq("cross1[211].cross2[jvm]"), - Right(Seq(_.cross1("211").cross2("jvm").suffixCmd())) + Right(Set(_.cross1("211").cross2("jvm").suffixCmd())) ) } } diff --git a/main/test/src/mill/util/ParseArgsTest.scala b/main/test/src/mill/util/ParseArgsTest.scala index 457c860f02d..4acad350dae 100644 --- a/main/test/src/mill/util/ParseArgsTest.scala +++ b/main/test/src/mill/util/ParseArgsTest.scala @@ -259,8 +259,8 @@ object ParseArgsTest extends TestSuite { List( ( List( - None -> Segments(Label("core"), Label("compile")), - None -> Segments(Label("application"), Label("compile")) + None -> Segments(Seq(Label("core"), Label("compile"))), + None -> Segments(Seq(Label("application"), Label("compile"))) ), Nil ) diff --git a/scalalib/src/mill/scalalib/UnresolvedPath.scala b/scalalib/src/mill/scalalib/UnresolvedPath.scala index 9b2b8c6bc20..69aa86f7bd6 100644 --- a/scalalib/src/mill/scalalib/UnresolvedPath.scala +++ b/scalalib/src/mill/scalalib/UnresolvedPath.scala @@ -31,8 +31,8 @@ object UnresolvedPath { ) extends UnresolvedPath { override def resolve(pathResolver: EvaluatorPathsResolver): Path = { pathResolver.resolveDest( - Segments(segments.map(Segment.Label(_)): _*), - foreignSegments.map(o => Segments(o.map(Segment.Label(_)): _*)) + Segments(segments.map(Segment.Label(_))), + foreignSegments.map(o => Segments(o.map(Segment.Label(_)))) ).dest / os.SubPath(subPath) } } From 1d4b411366fa0c09c54f5802e8ad72c5da9037f9 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 08:09:58 +0800 Subject: [PATCH 10/57] iwp --- main/src/mill/main/Resolve.scala | 85 ++++++++++++------------- main/test/src/mill/main/MainTests.scala | 14 ++-- 2 files changed, 46 insertions(+), 53 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 099ea5a64cb..1ddc74ac6dc 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -26,7 +26,6 @@ object Resolve { Nil ) match { case Resolve.Success(value) => - println("S") EitherOps.sequence( value.collect { case t: Resolve.Resolved.Target => Right(t.value) @@ -40,12 +39,11 @@ object Resolve { next match { case Segment.Label(s) => val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } - pprint.log(possibleStrings) - pprint.log(segments.render) - errorMsgLabel(s, possibleStrings, segments.value.toList) + errorMsgLabel(s, possibleStrings, segments) case Segment.Cross(keys) => - errorMsgCross(keys, possibleNexts.collect { case Segment.Cross(keys) => keys }.toSeq, segments.value.toList) + val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } + errorMsgCross(keys, possibleCrossKeys, segments) } case _ => @@ -101,18 +99,14 @@ object Resolve { case s: NotFound => Right(s) case s: Error => Left(s.msg) } + if (errors.nonEmpty) Error(errors.mkString("\n")) else { val successes = successesLists.flatten if (successes.nonEmpty) Success(successes) else { notFounds.headOption.getOrElse { - val mod = current.asInstanceOf[Resolved.Module].value - val directChildren: Set[Segment] = resolveDirectChildren(mod, None, discover, args) - .map(e => Segment.Label(e.right.get.name)) - pprint.log(mod) - pprint.log(directChildren) - NotFound(Segments(revSelectorsSoFar0.reverse), Set(current), head, directChildren) + notFoundResult(revSelectorsSoFar0, current, head, discover, args) } } } @@ -152,21 +146,7 @@ object Resolve { recurse(searchModules.map(m => Resolved.Module("", m)).toSet) - case _ => - NotFound( - Segments(revSelectorsSoFar0.reverse), - Set(current), - head, - current match{ - case Resolved.Module(_, c: Cross[_]) => - c.segmentsToModules.keys.toSet.map(Segment.Cross) - - case Resolved.Module(_, obj) => - resolveDirectChildren(obj, None, discover, args).map(e => Segment.Label(e.right.get.name)) - - case _ => Set() - } - ) + case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) } } @@ -207,6 +187,26 @@ object Resolve { (modules ++ targets ++ commands).toSet } + def notFoundResult(revSelectorsSoFar0: List[Segment], + current: Resolved, + next: Segment, + discover: Discover[_], + args: Seq[String]) = + NotFound( + Segments(revSelectorsSoFar0.reverse), + Set(current), + next, + current match { + case Resolved.Module(_, c: Cross[_]) => + c.segmentsToModules.keys.toSet.map(Segment.Cross) + + case Resolved.Module(_, obj) => + resolveDirectChildren(obj, None, discover, args).map(e => Segment.Label(e.right.get.name)) + + case _ => Set() + } + ) + def invokeCommand( target: Module, name: String, @@ -274,17 +274,11 @@ object Resolve { def errorMsgLabel(given: String, possibleMembers: Set[String], - revSelectorsSoFar: List[Segment]) = { - def render(x: String) = { - val rendered = Segments((Segment.Label(x) :: revSelectorsSoFar).reverse).render - pprint.log(x) - pprint.log(revSelectorsSoFar) - pprint.log(rendered) - rendered - } + segments: Segments) = { + def render(x: String) = (segments ++ Seq(Segment.Label(x))).render val suggestion = findMostSimilar(given, possibleMembers) match { - case None => hintListLabel(revSelectorsSoFar) + case None => hintListLabel(segments.value) case Some(similar) => " Did you mean " + render(similar) + "?" } @@ -294,20 +288,19 @@ object Resolve { } def errorMsgCross(givenKeys: Seq[String], - possibleCrossKeys: Seq[Seq[String]], - revSelectorsSoFar: List[Segment]) = { + possibleCrossKeys: Set[Seq[String]], + segments: Segments) = { - def render(xs: Seq[String]) = { - Segments(Segment.Cross(xs) :: revSelectorsSoFar).render - } + def render(xs: Seq[String]) = (segments ++ Seq(Segment.Cross(xs))).render - val similarKeysOpts = givenKeys - .zip(possibleCrossKeys) - .map{case (givenKey, possibleKeys) => findMostSimilar(givenKey, possibleKeys.toSet)} - val suggestion = - if (similarKeysOpts.exists(_.isEmpty)) hintListCross(revSelectorsSoFar) - else " Did you mean " + render(similarKeysOpts.map{case Some(v) => v}) + "?" + val suggestion = findMostSimilar( + givenKeys.mkString(","), + possibleCrossKeys.map(_.mkString(",")) + ) match { + case None => hintListLabel(segments.value) + case Some(similar) => " Did you mean " + render(similar.split(',')) + "?" + } unableToResolve(render(givenKeys)) + suggestion } diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/MainTests.scala index a3a4245d15d..1776c547d4b 100644 --- a/main/test/src/mill/main/MainTests.scala +++ b/main/test/src/mill/main/MainTests.scala @@ -155,7 +155,7 @@ object MainTests extends TestSuite { "neg2" - check( "cross[doesntExist].doesntExist", Left( - "Cannot resolve cross[doesntExist]. Try `mill resolve cross[__]` to see what's available." + "Cannot resolve cross[doesntExist]. Try `mill resolve cross._` to see what's available." ) ) "neg3" - check( @@ -165,7 +165,7 @@ object MainTests extends TestSuite { "neg4" - check( "cross[doesntExist].suffix", Left( - "Cannot resolve cross[doesntExist]. Try `mill resolve cross[__]` to see what's available." + "Cannot resolve cross[doesntExist]. Try `mill resolve cross._` to see what's available." ) ) "wildcard" - check( @@ -198,28 +198,28 @@ object MainTests extends TestSuite { "wildcard" - { "labelNeg1" - check( "_.suffix", - Left("Cannot resolve cross.suffix. Try `mill resolve cross._` to see what's available.") + Left("Cannot resolve _.suffix. Try `mill resolve _._` to see what's available.") ) "labelNeg2" - check( "_.doesntExist", Left( - "Cannot resolve cross.doesntExist. Try `mill resolve cross._` to see what's available." + "Cannot resolve _.doesntExist. Try `mill resolve _._` to see what's available." ) ) "labelNeg3" - check( "__.doesntExist", - Left("Cannot resolve __.doesntExist. Try `mill resolve _` to see what's available.") + Left("Cannot resolve __.doesntExist. Try `mill resolve __._` to see what's available.") ) "labelNeg4" - check( "cross.__.doesntExist", Left( - "Cannot resolve cross.__.doesntExist. Try `mill resolve cross._` to see what's available." + "Cannot resolve cross.__.doesntExist. Try `mill resolve cross.__._` to see what's available." ) ) "labelNeg5" - check( "cross._.doesntExist", Left( - "Cannot resolve cross._.doesntExist. Try `mill resolve cross._` to see what's available." + "Cannot resolve cross._. Try `mill resolve cross._` to see what's available." ) ) "labelPos" - check( From 363e4826fcc86d758a651a0955043e374b8de473 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 08:33:54 +0800 Subject: [PATCH 11/57] fixes --- main/core/src/mill/define/Module.scala | 1 - main/src/mill/main/Resolve.scala | 22 +++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 02fbabd2576..c802bbc574e 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -52,7 +52,6 @@ object Module { n = decode(m.getName) if filter(n) && ParseArgs.isLegalIdentifier(n) && - m.getParameterCount == 0 && (m.getModifiers & Modifier.STATIC) == 0 && (m.getModifiers & Modifier.ABSTRACT) == 0 && inner.isAssignableFrom(m.getReturnType) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 1ddc74ac6dc..ec24dc4bb77 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -26,12 +26,18 @@ object Resolve { Nil ) match { case Resolve.Success(value) => - EitherOps.sequence( - value.collect { - case t: Resolve.Resolved.Target => Right(t.value) - case t: Resolve.Resolved.Command => t.value() - } - ).map(_.toSet[NamedTask[Any]]) + val taskList: Set[Either[String, NamedTask[_]]] = value.collect { + case Resolved.Target(name, value) => Right(value) + case Resolved.Command(name, value) => value() + case Resolved.Module(name, value: TaskModule) => + resolveDirectChildren(value, Some(value.defaultCommandName()), discover, args).head.flatMap{ + case Resolved.Target(name, value) => Right(value) + case Resolved.Command(name, value) => value() + } + } + + if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) + else Left(s"Cannot find default task to evaluate for module ${Segments(remainingSelector).render}") case Resolve.NotFound(segments, found, next, possibleNexts) => val errorMsg = found.head match{ @@ -69,7 +75,9 @@ object Resolve { } sealed trait Result - case class Success(value: Set[Resolved]) extends Result + case class Success(value: Set[Resolved]) extends Result{ + assert(value.nonEmpty) + } sealed trait Failed extends Result case class NotFound(deepest: Segments, found: Set[Resolved], From d35f58b57d633b539ccd003bc98327e1fe98fe82 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 10:07:42 +0800 Subject: [PATCH 12/57] all MainTests pass --- main/src/mill/main/Resolve.scala | 125 +++++++++++------------- main/test/src/mill/main/MainTests.scala | 18 ++-- 2 files changed, 67 insertions(+), 76 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index ec24dc4bb77..740ae5d75a7 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -14,30 +14,30 @@ import scala.collection.immutable * Reports an error if nothing is resolved. */ object Resolve { - def resolveTasks(remainingSelector: List[Segment], + def resolveTasks(selector: List[Segment], current: BaseModule, discover: Discover[_], args: Seq[String]): Either[String, Set[NamedTask[Any]]] = { Resolve.resolve( - remainingSelector, - Resolve.Resolved.Module("", current), + selector, + Resolve.Resolved.Module(current), discover, args, Nil ) match { case Resolve.Success(value) => val taskList: Set[Either[String, NamedTask[_]]] = value.collect { - case Resolved.Target(name, value) => Right(value) - case Resolved.Command(name, value) => value() - case Resolved.Module(name, value: TaskModule) => - resolveDirectChildren(value, Some(value.defaultCommandName()), discover, args).head.flatMap{ - case Resolved.Target(name, value) => Right(value) - case Resolved.Command(name, value) => value() + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + case Resolved.Module(value: TaskModule) => + resolveDirectChildren(value, Some(value.defaultCommandName()), discover, args).values.head.flatMap{ + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() } } if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) - else Left(s"Cannot find default task to evaluate for module ${Segments(remainingSelector).render}") + else Left(s"Cannot find default task to evaluate for module ${Segments(selector).render}") case Resolve.NotFound(segments, found, next, possibleNexts) => val errorMsg = found.head match{ @@ -46,15 +46,14 @@ object Resolve { case Segment.Label(s) => val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } - errorMsgLabel(s, possibleStrings, segments) + errorMsgLabel(s, possibleStrings, segments, Segments(selector)) case Segment.Cross(keys) => val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } - errorMsgCross(keys, possibleCrossKeys, segments) + errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) } - case _ => - + case x => unableToResolve((segments ++ Seq(next)).render) + - " Task " + segments.render + " is not a module and has no children." + s" ${segments.render} resolves to a Task with no children." } Left(errorMsg) @@ -63,14 +62,12 @@ object Resolve { } } - sealed trait Resolved{ - def name: String - } + sealed trait Resolved object Resolved { - case class Module(name: String, value: mill.define.Module) extends Resolved - case class Target(name: String, value: mill.define.Target[_]) extends Resolved - case class Command(name: String, value: () => Either[String, mill.define.Command[_]]) + case class Module(value: mill.define.Module) extends Resolved + case class Target(value: mill.define.Target[_]) extends Resolved + case class Command(value: () => Either[String, mill.define.Command[_]]) extends Resolved } @@ -94,8 +91,7 @@ object Resolve { args: Seq[String], revSelectorsSoFar0: List[Segment] ): Result = remainingSelector match { - case Nil => - Success(Set(current)) + case Nil => Success(Set(current)) case head :: tail => val revSelectorsSoFar = head :: revSelectorsSoFar0 def recurse(searchModules: Set[Resolved]): Result = { @@ -109,20 +105,15 @@ object Resolve { } if (errors.nonEmpty) Error(errors.mkString("\n")) - else { - val successes = successesLists.flatten - if (successes.nonEmpty) Success(successes) - else { - notFounds.headOption.getOrElse { - notFoundResult(revSelectorsSoFar0, current, head, discover, args) - } - } + else if (successesLists.flatten.nonEmpty) Success(successesLists.flatten) + else notFounds.size match{ + case 1 => notFounds.head + case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) } } (head, current) match { - case (Segment.Label(singleLabel), Resolved.Module(_, obj)) => - lazy val directChildren = resolveDirectChildren(obj, None, discover, args) + case (Segment.Label(singleLabel), Resolved.Module(obj)) => EitherOps.sequence( singleLabel match { case "__" => @@ -130,18 +121,18 @@ object Resolve { .millInternal .modules .flatMap(m => - Seq(Right(Resolved.Module(m.millModuleSegments.parts.lastOption.getOrElse(""), m))) ++ - resolveDirectChildren(m, None, discover, args) + Seq(Right(Resolved.Module(m))) ++ + resolveDirectChildren(m, None, discover, args).values ) - case "_" => directChildren - case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args) + case "_" => resolveDirectChildren(obj, None, discover, args).values + case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args).values } ) match{ case Left(err) => Error(err) case Right(v) => recurse(v.toSet) } - case (Segment.Cross(cross), Resolved.Module(_, c: Cross[_])) => + case (Segment.Cross(cross), Resolved.Module(c: Cross[_])) => val searchModules: Seq[Module] = if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v else if (cross.contains("_")) { @@ -152,7 +143,7 @@ object Resolve { } yield v } else c.segmentsToModules.get(cross.toList).toSeq - recurse(searchModules.map(m => Resolved.Module("", m)).toSet) + recurse(searchModules.map(m => Resolved.Module(m)).toSet) case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) } @@ -164,24 +155,28 @@ object Resolve { nameOpt: Option[String] = None, discover: Discover[_], args: Seq[String] - ): Set[Either[String, Resolved]] = { + ): Map[Segment, Either[String, Resolved]] = { def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n) val modules = obj .millInternal .reflectNestedObjects[Module](namePred) - .map(t => Right(Resolved.Module(t.millModuleSegments.parts.last, t))) + .map(t => Segment.Label(t.millModuleSegments.parts.last) -> Right(Resolved.Module(t))) + + val crosses = obj match { + case c: Cross[_] if nameOpt.isEmpty => c.segmentsToModules.map{case (k, v) => Segment.Cross(k) -> Right(Resolved.Module(v))} + case _ => Nil + } val targets = Module .reflect(obj.getClass, classOf[Target[_]], namePred) - .map(m => Right(Resolved.Target(m.getName, m.invoke(obj).asInstanceOf[Target[_]]))) + .map(m => Segment.Label(m.getName) -> Right(Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]))) val commands = Module .reflect(obj.getClass, classOf[Command[_]], namePred) .map(_.getName) .map(name => - Right(Resolved.Command( - name, + Segment.Label(name) -> Right(Resolved.Command( () => invokeCommand( obj, @@ -192,7 +187,7 @@ object Resolve { )) ) - (modules ++ targets ++ commands).toSet + (modules ++ crosses ++ targets ++ commands).toMap } def notFoundResult(revSelectorsSoFar0: List[Segment], @@ -205,11 +200,8 @@ object Resolve { Set(current), next, current match { - case Resolved.Module(_, c: Cross[_]) => - c.segmentsToModules.keys.toSet.map(Segment.Cross) - - case Resolved.Module(_, obj) => - resolveDirectChildren(obj, None, discover, args).map(e => Segment.Label(e.right.get.name)) + case Resolved.Module(obj) => + resolveDirectChildren(obj, None, discover, args).keySet case _ => Set() } @@ -267,10 +259,6 @@ object Resolve { hintList(revSelectorsSoFar :+ Segment.Label("_")) } - def hintListCross(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Cross(Seq("__"))) - } - def findMostSimilar(given: String, options: Set[String]): Option[String] = { options @@ -282,35 +270,38 @@ object Resolve { def errorMsgLabel(given: String, possibleMembers: Set[String], - segments: Segments) = { - def render(x: String) = (segments ++ Seq(Segment.Label(x))).render - + prefixSegments: Segments, + fullSegements: Segments) = { val suggestion = findMostSimilar(given, possibleMembers) match { - case None => hintListLabel(segments.value) - case Some(similar) => " Did you mean " + render(similar) + "?" + case None => hintListLabel(prefixSegments.value) + case Some(similar) => + " Did you mean " + + (prefixSegments ++ Seq(Segment.Label(similar))).render+ + "?" } - val msg = unableToResolve(render(given)) + suggestion + val msg = unableToResolve(fullSegements.render) + suggestion msg } def errorMsgCross(givenKeys: Seq[String], possibleCrossKeys: Set[Seq[String]], - segments: Segments) = { - - def render(xs: Seq[String]) = (segments ++ Seq(Segment.Cross(xs))).render - + prefixSegments: Segments, + fullSegements: Segments) = { val suggestion = findMostSimilar( givenKeys.mkString(","), possibleCrossKeys.map(_.mkString(",")) ) match { - case None => hintListLabel(segments.value) - case Some(similar) => " Did you mean " + render(similar.split(',')) + "?" + case None => hintListLabel(prefixSegments.value) + case Some(similar) => + " Did you mean " + + (prefixSegments ++ Seq(Segment.Cross(similar.split(',')))).render + + "?" } - unableToResolve(render(givenKeys)) + suggestion + unableToResolve(fullSegements.render) + suggestion } } diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/MainTests.scala index 1776c547d4b..5cf037bd627 100644 --- a/main/test/src/mill/main/MainTests.scala +++ b/main/test/src/mill/main/MainTests.scala @@ -113,15 +113,15 @@ object MainTests extends TestSuite { ) "wildcardNeg" - check( "_._.single", - Left("Cannot resolve _._.single. Task _._ is not a module and has no children.") + Left("Cannot resolve _._.single. Try `mill resolve _` to see what's available.") ) "wildcardNeg2" - check( "_._.__", - Left("Cannot resolve _._.__. Task _._ is not a module and has no children.") + Left("Cannot resolve _._.__. Try `mill resolve _` to see what's available.") ) "wildcardNeg3" - check( "nested._.foobar", - Left("Cannot resolve nested._.foobar. Task nested._ is not a module and has no children.") + Left("Cannot resolve nested._.foobar. nested._ resolves to a Task with no children.") ) "wildcard2" - check( "__.single", @@ -155,17 +155,17 @@ object MainTests extends TestSuite { "neg2" - check( "cross[doesntExist].doesntExist", Left( - "Cannot resolve cross[doesntExist]. Try `mill resolve cross._` to see what's available." + "Cannot resolve cross[doesntExist].doesntExist. Try `mill resolve cross._` to see what's available." ) ) "neg3" - check( "cross[221].doesntExist", - Left("Cannot resolve cross[221]. Did you mean cross[211]?") + Left("Cannot resolve cross[221].doesntExist. Did you mean cross[211]?") ) "neg4" - check( "cross[doesntExist].suffix", Left( - "Cannot resolve cross[doesntExist]. Try `mill resolve cross._` to see what's available." + "Cannot resolve cross[doesntExist].suffix. Try `mill resolve cross._` to see what's available." ) ) "wildcard" - check( @@ -208,18 +208,18 @@ object MainTests extends TestSuite { ) "labelNeg3" - check( "__.doesntExist", - Left("Cannot resolve __.doesntExist. Try `mill resolve __._` to see what's available.") + Left("Cannot resolve __.doesntExist. Try `mill resolve _` to see what's available.") ) "labelNeg4" - check( "cross.__.doesntExist", Left( - "Cannot resolve cross.__.doesntExist. Try `mill resolve cross.__._` to see what's available." + "Cannot resolve cross.__.doesntExist. Try `mill resolve cross._` to see what's available." ) ) "labelNeg5" - check( "cross._.doesntExist", Left( - "Cannot resolve cross._. Try `mill resolve cross._` to see what's available." + "Cannot resolve cross._.doesntExist. Try `mill resolve cross._` to see what's available." ) ) "labelPos" - check( From cd9c0cabe1d8a217ad26027b6d040458d89dbaa8 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 10:18:57 +0800 Subject: [PATCH 13/57] add doubleNestedModule tests, enable more tests --- main/src/mill/main/Resolve.scala | 8 ++-- main/test/src/mill/main/MainTests.scala | 53 ++++++++++++++++++------ main/test/src/mill/util/TestGraphs.scala | 10 +++++ 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 740ae5d75a7..7d44d89e76b 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -271,7 +271,7 @@ object Resolve { def errorMsgLabel(given: String, possibleMembers: Set[String], prefixSegments: Segments, - fullSegements: Segments) = { + fullSegments: Segments) = { val suggestion = findMostSimilar(given, possibleMembers) match { case None => hintListLabel(prefixSegments.value) case Some(similar) => @@ -280,7 +280,7 @@ object Resolve { "?" } - val msg = unableToResolve(fullSegements.render) + suggestion + val msg = unableToResolve(fullSegments.render) + suggestion msg } @@ -288,7 +288,7 @@ object Resolve { def errorMsgCross(givenKeys: Seq[String], possibleCrossKeys: Set[Seq[String]], prefixSegments: Segments, - fullSegements: Segments) = { + fullSegments: Segments) = { val suggestion = findMostSimilar( givenKeys.mkString(","), @@ -301,7 +301,7 @@ object Resolve { "?" } - unableToResolve(fullSegements.render) + suggestion + unableToResolve(fullSegments.render) + suggestion } } diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/MainTests.scala index 5cf037bd627..3d0ec839aca 100644 --- a/main/test/src/mill/main/MainTests.scala +++ b/main/test/src/mill/main/MainTests.scala @@ -47,7 +47,10 @@ object MainTests extends TestSuite { "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") ) -// "neg6" - check("single.doesntExist", Left("Task single is not a module and has no children.")) + "neg6" - check( + "single.doesntExist", + Left("Cannot resolve single.doesntExist. single resolves to a Task with no children.") + ) "neg7" - check("", Left("Selector cannot be empty")) } // "backtickIdentifiers" - { @@ -84,10 +87,10 @@ object MainTests extends TestSuite { "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") ) -// "neg2" - check( -// "single.doesntExist", -// Left("Task single is not a module and has no children.") -// ) + "neg2" - check( + "single.doesntExist", + Left("Cannot resolve single.doesntExist. single resolves to a Task with no children.") + ) "neg3" - check( "nested.doesntExist", Left( @@ -139,8 +142,36 @@ object MainTests extends TestSuite { _.nested.single )) ) - } + "doubleNested" - { + val check = MainTests.check(doubleNestedModule) _ + "pos1" - check("single", Right(Set(_.single))) + "pos2" - check("nested.single", Right(Set(_.nested.single))) + "pos3" - check("nested.inner.single", Right(Set(_.nested.inner.single))) + "neg1" - check( + "doesntExist", + Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") + ) + "neg2" - check( + "nested.doesntExist", + Left( + "Cannot resolve nested.doesntExist. Try `mill resolve nested._` to see what's available." + ) + ) + "neg3" - check( + "nested.inner.doesntExist", + Left( + "Cannot resolve nested.inner.doesntExist. Try `mill resolve nested.inner._` to see what's available." + ) + ) + "neg4" - check( + "nested.inner.doesntExist.alsoDoesntExist2", + Left( + "Cannot resolve nested.inner.doesntExist.alsoDoesntExist2. Try `mill resolve nested.inner._` to see what's available." + ) + ) + } + "cross" - { "single" - { val check = MainTests.check(singleCross) _ @@ -333,12 +364,10 @@ object MainTests extends TestSuite { Seq("cross1[210].cross2[js]"), Right(Set(_.cross1("210").cross2("js").suffixCmd())) ) - // does not work because we're reflecting the Module for `def` without args, - // which misses command with args :-( -// "pos1WithWildcard" - check( -// Set("cross1[210].cross2[js]._"), -// Right(Set(_.cross1("210").cross2("js").suffixCmd())) -// ) + "pos1WithWildcard" - check( + Seq("cross1[210].cross2[js]._"), + Right(Set(_.cross1("210").cross2("js").suffixCmd())) + ) "pos1WithArgs" - check( Seq("cross1[210].cross2[js].suffixCmd", "suffix-arg"), Right(Set(_.cross1("210").cross2("js").suffixCmd("suffix-arg"))) diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index c6b540966f6..98bcd43ceea 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -176,6 +176,16 @@ object TestGraphs { object classInstance extends CanNest } + object doubleNestedModule extends TestUtil.BaseModule { + def single = T { 5 } + object nested extends Module { + def single = T { 7 } + + object inner extends Module { + def single = T { 9 } + } + } + } trait BaseModule extends Module { def foo = T { Seq("base") } From add13e15477d17c6a39bdb7bb2676e021e83ec76 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 10:40:46 +0800 Subject: [PATCH 14/57] try and re-enable other uses of resolve --- main/src/mill/main/MainModule.scala | 76 ++++----- main/src/mill/main/Resolve.scala | 128 ++------------- main/src/mill/main/Resolvers.scala | 151 ++++++++++++++++++ main/src/mill/main/RunScript.scala | 19 ++- main/src/mill/main/TokenReaders.scala | 1 + .../{MainTests.scala => ResolversTests.scala} | 68 ++++---- 6 files changed, 254 insertions(+), 189 deletions(-) create mode 100644 main/src/mill/main/Resolvers.scala rename main/test/src/mill/main/{MainTests.scala => ResolversTests.scala} (88%) diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index d292a2ed862..f09823225f6 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -21,7 +21,7 @@ object MainModule { targets: Seq[String], selectMode: SelectMode )(f: List[NamedTask[Any]] => T): Result[T] = { - RunScript.resolveTasks(evaluator, targets, selectMode) match { + RunScript.resolveTasks(ResolveTasks, evaluator, targets, selectMode) match { case Left(err) => Result.Failure(err) case Right(tasks) => Result.Success(f(tasks)) } @@ -74,18 +74,19 @@ trait MainModule extends mill.Module { * Resolves a mill query string and prints out the tasks it resolves to. */ def resolve(evaluator: Evaluator, targets: String*): Command[List[String]] = T.command { -// val resolved: Either[String, List[String]] = RunScript.resolveTasks( -// evaluator, -// targets, -// SelectMode.Multi -// ) -// -// resolved match { -// case Left(err) => Result.Failure(err) -// case Right(rs) => -// rs.sorted.foreach(T.log.outputStream.println) -// Result.Success(rs) -// } + val resolved: Either[String, List[String]] = RunScript.resolveTasks( + ResolveMetadata, + evaluator, + targets, + SelectMode.Multi + ) + + resolved match { + case Left(err) => Result.Failure(err) + case Right(rs) => + rs.sorted.foreach(T.log.outputStream.println) + Result.Success(rs) + } List.empty[String] } @@ -105,6 +106,7 @@ trait MainModule extends mill.Module { private def plan0(evaluator: Evaluator, targets: Seq[String]) = { RunScript.resolveTasks( + ResolveTasks, evaluator, targets, SelectMode.Multi @@ -124,6 +126,7 @@ trait MainModule extends mill.Module { */ def path(evaluator: Evaluator, src: String, dest: String): Command[List[String]] = T.command { val resolved = RunScript.resolveTasks( + ResolveTasks, evaluator, List(src, dest), SelectMode.Multi @@ -313,29 +316,29 @@ trait MainModule extends mill.Module { } val pathsToRemove = Right(os.list(rootDir).filterNot(keepPath)) -// if (targets.isEmpty) -// Right(os.list(rootDir).filterNot(keepPath)) -// else -// RunScript.resolveTasks( -// mill.main.ResolveSegments, -// evaluator, -// targets, -// SelectMode.Multi -// ).map { ts => -// ts.flatMap { segments => -// val evPpaths = EvaluatorPaths.resolveDestPaths(rootDir, segments) -// val paths = Seq(evPpaths.dest, evPpaths.meta, evPpaths.log) -// val potentialModulePath = rootDir / EvaluatorPaths.makeSegmentStrings(segments) -// if (os.exists(potentialModulePath)) { -// // this is either because of some pre-Mill-0.10 files lying around -// // or most likely because the segments denote a module but not a task -// // in which case we want to remove the module and all its sub-modules -// // (If this logic is later found to be to harsh, we could further guard it, -// // to when non of the other paths exists.) -// paths :+ potentialModulePath -// } else paths -// } -// } + if (targets.isEmpty) + Right(os.list(rootDir).filterNot(keepPath)) + else + RunScript.resolveTasks( + mill.main.ResolveSegments, + evaluator, + targets, + SelectMode.Multi + ).map { ts => + ts.flatMap { segments => + val evPpaths = EvaluatorPaths.resolveDestPaths(rootDir, segments) + val paths = Seq(evPpaths.dest, evPpaths.meta, evPpaths.log) + val potentialModulePath = rootDir / EvaluatorPaths.makeSegmentStrings(segments) + if (os.exists(potentialModulePath)) { + // this is either because of some pre-Mill-0.10 files lying around + // or most likely because the segments denote a module but not a task + // in which case we want to remove the module and all its sub-modules + // (If this logic is later found to be to harsh, we could further guard it, + // to when non of the other paths exists.) + paths :+ potentialModulePath + } else paths + } + } pathsToRemove match { // case Left(err) => @@ -409,6 +412,7 @@ trait MainModule extends mill.Module { } RunScript.resolveTasks( + ResolveTasks, evaluator, targets, SelectMode.Multi diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 7d44d89e76b..a652227ea28 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -3,6 +3,7 @@ package mill.main import mainargs.{MainData, TokenGrouping} import mill.define._ import mill.util.EitherOps +import scala.reflect.NameTransformer.decode import scala.collection.immutable @@ -11,64 +12,27 @@ import scala.collection.immutable * resolves all possible modules, targets or commands that the segments could * resolve to. * - * Reports an error if nothing is resolved. + * Returns a [[Result]], either containing a [[Success]] containing the + * [[Resolved]] set, [[NotFound]] if it couldn't find anything with some + * metadata about what it was looking for, or [[Error]] if something blew up. */ object Resolve { - def resolveTasks(selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String]): Either[String, Set[NamedTask[Any]]] = { - Resolve.resolve( - selector, - Resolve.Resolved.Module(current), - discover, - args, - Nil - ) match { - case Resolve.Success(value) => - val taskList: Set[Either[String, NamedTask[_]]] = value.collect { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - case Resolved.Module(value: TaskModule) => - resolveDirectChildren(value, Some(value.defaultCommandName()), discover, args).values.head.flatMap{ - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - } - } - - if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) - else Left(s"Cannot find default task to evaluate for module ${Segments(selector).render}") - - case Resolve.NotFound(segments, found, next, possibleNexts) => - val errorMsg = found.head match{ - case s: Resolved.Module => - next match { - case Segment.Label(s) => - val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } - - errorMsgLabel(s, possibleStrings, segments, Segments(selector)) - case Segment.Cross(keys) => - val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } - errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) - } - case x => - unableToResolve((segments ++ Seq(next)).render) + - s" ${segments.render} resolves to a Task with no children." - } - - Left(errorMsg) - case Resolve.Error(value) => Left(value) - } + sealed trait Resolved{ + def segments: Segments } - sealed trait Resolved - object Resolved { - case class Module(value: mill.define.Module) extends Resolved - case class Target(value: mill.define.Target[_]) extends Resolved + case class Module(value: mill.define.Module) extends Resolved{ + def segments = value.millModuleSegments + } + case class Target(value: mill.define.Target[_]) extends Resolved{ + def segments = value.ctx.segments + } case class Command(value: () => Either[String, mill.define.Command[_]]) - extends Resolved + extends Resolved{ + def segments = value().right.get.ctx.segments + } } sealed trait Result @@ -170,11 +134,11 @@ object Resolve { val targets = Module .reflect(obj.getClass, classOf[Target[_]], namePred) - .map(m => Segment.Label(m.getName) -> Right(Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]))) + .map(m => Segment.Label(decode(m.getName)) -> Right(Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]))) val commands = Module .reflect(obj.getClass, classOf[Command[_]], namePred) - .map(_.getName) + .map(m => decode(m.getName)) .map(name => Segment.Label(name) -> Right(Resolved.Command( () => @@ -246,62 +210,4 @@ object Resolve { ) } } - - def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." - - - def hintList(revSelectorsSoFar: Seq[Segment]) = { - val search = Segments(revSelectorsSoFar).render - s" Try `mill resolve $search` to see what's available." - } - - def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Label("_")) - } - - def findMostSimilar(given: String, - options: Set[String]): Option[String] = { - options - .map { option => (option, LevenshteinDistance.editDistance(given, option))} - .filter(_._2 < 3) - .minByOption(_._2) - .map(_._1) - } - - def errorMsgLabel(given: String, - possibleMembers: Set[String], - prefixSegments: Segments, - fullSegments: Segments) = { - val suggestion = findMostSimilar(given, possibleMembers) match { - case None => hintListLabel(prefixSegments.value) - case Some(similar) => - " Did you mean " + - (prefixSegments ++ Seq(Segment.Label(similar))).render+ - "?" - } - - val msg = unableToResolve(fullSegments.render) + suggestion - - msg - } - - def errorMsgCross(givenKeys: Seq[String], - possibleCrossKeys: Set[Seq[String]], - prefixSegments: Segments, - fullSegments: Segments) = { - - val suggestion = findMostSimilar( - givenKeys.mkString(","), - possibleCrossKeys.map(_.mkString(",")) - ) match { - case None => hintListLabel(prefixSegments.value) - case Some(similar) => - " Did you mean " + - (prefixSegments ++ Seq(Segment.Cross(similar.split(',')))).render + - "?" - } - - unableToResolve(fullSegments.render) + suggestion - } - } diff --git a/main/src/mill/main/Resolvers.scala b/main/src/mill/main/Resolvers.scala new file mode 100644 index 00000000000..95e4bf35c30 --- /dev/null +++ b/main/src/mill/main/Resolvers.scala @@ -0,0 +1,151 @@ +package mill.main + +import mill.define.{BaseModule, Discover, NamedTask, Segment, Segments, TaskModule} +import mill.main.Resolve.Resolved +import mill.util.EitherOps + +import scala.util.Either + + +object ResolveSegments extends Resolver[Segments] { + def resolve(selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String]) = { + resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments) } + } +} + +object ResolveMetadata extends Resolver[String] { + def resolve(selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String]) = { + resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments.render) } + } +} + +object ResolveTasks extends Resolver[NamedTask[Any]]{ + def resolve(selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String]) = { + resolveNonEmpty(selector, current, discover, args).flatMap{ value => + val taskList: Set[Either[String, NamedTask[_]]] = value.collect { + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + case Resolved.Module(value: TaskModule) => + Resolve.resolveDirectChildren(value, Some(value.defaultCommandName()), discover, args).values.head.flatMap { + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + } + } + + if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) + else Left(s"Cannot find default task to evaluate for module ${Segments(selector).render}") + } + + } +} + + +trait Resolver[T]{ + def resolve(selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String]): Either[String, Set[T]] + + def resolveNonEmpty(selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String]): Either[String, Set[Resolved]] = { + Resolve.resolve( + selector, + Resolve.Resolved.Module(current), + discover, + args, + Nil + ) match { + case Resolve.Success(value) => Right(value) + + case Resolve.NotFound(segments, found, next, possibleNexts) => + val errorMsg = found.head match { + case s: Resolved.Module => + next match { + case Segment.Label(s) => + val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } + + errorMsgLabel(s, possibleStrings, segments, Segments(selector)) + case Segment.Cross(keys) => + val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } + errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) + } + case x => + unableToResolve((segments ++ Seq(next)).render) + + s" ${segments.render} resolves to a Task with no children." + } + + Left(errorMsg) + + case Resolve.Error(value) => Left(value) + } + } + + + def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." + + + def hintList(revSelectorsSoFar: Seq[Segment]) = { + val search = Segments(revSelectorsSoFar).render + s" Try `mill resolve $search` to see what's available." + } + + def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { + hintList(revSelectorsSoFar :+ Segment.Label("_")) + } + + def findMostSimilar(given: String, + options: Set[String]): Option[String] = { + options + .map { option => (option, LevenshteinDistance.editDistance(given, option)) } + .filter(_._2 < 3) + .minByOption(_._2) + .map(_._1) + } + + def errorMsgLabel(given: String, + possibleMembers: Set[String], + prefixSegments: Segments, + fullSegments: Segments) = { + val suggestion = findMostSimilar(given, possibleMembers) match { + case None => hintListLabel(prefixSegments.value) + case Some(similar) => + " Did you mean " + + (prefixSegments ++ Seq(Segment.Label(similar))).render + + "?" + } + + val msg = unableToResolve(fullSegments.render) + suggestion + + msg + } + + def errorMsgCross(givenKeys: Seq[String], + possibleCrossKeys: Set[Seq[String]], + prefixSegments: Segments, + fullSegments: Segments) = { + + val suggestion = findMostSimilar( + givenKeys.mkString(","), + possibleCrossKeys.map(_.mkString(",")) + ) match { + case None => hintListLabel(prefixSegments.value) + case Some(similar) => + " Did you mean " + + (prefixSegments ++ Seq(Segment.Cross(similar.split(',')))).render + + "?" + } + + unableToResolve(fullSegments.render) + suggestion + } +} diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index 471afe97f35..27f1e6d67d9 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -19,25 +19,28 @@ object RunScript { type TaskName = String - def resolveTasks[T]( + + def resolveTasks[T, R: ClassTag]( + resolver: mill.main.Resolver[R], evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode - ): Either[String, List[NamedTask[Any]]] = { + ): Either[String, List[R]] = { val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) val resolvedGroups = parsedGroups.flatMap { groups => val resolved = groups.map { parsed: TargetsWithParams => - resolveTasks(evaluator, Right(parsed)) + resolveTasks(resolver, evaluator, Right(parsed)) } EitherOps.sequence(resolved) } resolvedGroups.map(_.flatten.toList) } - private def resolveTasks[T]( + private def resolveTasks[T, R: ClassTag]( + resolver: mill.main.Resolver[R], evaluator: Evaluator, targetsWithParams: Either[String, TargetsWithParams] - ): Either[String, List[NamedTask[Any]]] = { + ): Either[String, List[R]] = { for { parsed <- targetsWithParams (selectors, args) = parsed @@ -54,7 +57,7 @@ object RunScript { // main build. Resolving targets from external builds as CLI arguments // is not currently supported mill.eval.Evaluator.currentEvaluator.set(evaluator) - Resolve.resolveTasks( + resolver.resolve( sel.value.toList, rootModule, rootModule.millDiscover, @@ -110,7 +113,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[ujson.Value])]])] = { - for (targets <- resolveTasks(evaluator, scriptArgs, selectMode)) + for (targets <- resolveTasks(ResolveTasks, evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluate(evaluator, Agg.from(targets.distinct)) @@ -129,7 +132,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[(TaskName, ujson.Value)])]])] = { - for (targets <- resolveTasks(evaluator, scriptArgs, selectMode)) + for (targets <- resolveTasks(ResolveTasks, evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluateNamed(evaluator, Agg.from(targets.distinct)) diff --git a/main/src/mill/main/TokenReaders.scala b/main/src/mill/main/TokenReaders.scala index 4721c5651fc..56db4d7b827 100644 --- a/main/src/mill/main/TokenReaders.scala +++ b/main/src/mill/main/TokenReaders.scala @@ -12,6 +12,7 @@ object Tasks { shortName = "", read = s => RunScript.resolveTasks( + ResolveTasks, Evaluator.currentEvaluator.get, s, SelectMode.Single diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/ResolversTests.scala similarity index 88% rename from main/test/src/mill/main/MainTests.scala rename to main/test/src/mill/main/ResolversTests.scala index 3d0ec839aca..bc3991c0001 100644 --- a/main/test/src/mill/main/MainTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -3,7 +3,7 @@ package mill.main import mill.define.{NamedTask, Segment, SelectMode} import mill.util.TestGraphs._ import utest._ -object MainTests extends TestSuite { +object ResolversTests extends TestSuite { def check[T <: mill.define.BaseModule](module: T)( selectorString: String, @@ -18,7 +18,7 @@ object MainTests extends TestSuite { val expected = expected0.map(_.map(_(module))) val resolved = for { selectors <- mill.define.ParseArgs(selectorStrings, SelectMode.Single).map(_.head._1.head) - task <- mill.main.Resolve.resolveTasks( + task <- mill.main.ResolveTasks.resolve( selectors._2.value.toList, module, module.millDiscover, @@ -34,7 +34,7 @@ object MainTests extends TestSuite { val graphs = new mill.util.TestGraphs() import graphs._ "single" - { - val check = MainTests.check(singleton) _ + val check = ResolversTests.check(singleton) _ "pos" - check("single", Right(Set(_.single))) "neg1" - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) "neg2" - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) @@ -53,33 +53,33 @@ object MainTests extends TestSuite { ) "neg7" - check("", Left("Selector cannot be empty")) } -// "backtickIdentifiers" - { -// val check = MainTests.check(bactickIdentifiers) _ -// "pos1" - check("up-target", Right(Seq(_.`up-target`))) -// "pos2" - check("a-down-target", Right(Seq(_.`a-down-target`))) -// "neg1" - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) -// "neg2" - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) -// "neg3" - check( -// "up-target.doesntExist", -// Left("Task up-target is not a module and has no children.") -// ) -// "neg4" - check("", Left("Selector cannot be empty")) -// "neg5" - check( -// "invisible&", -// Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.") -// ) -// "nested" - { -// "pos" - check("nested-module.nested-target", Right(Seq(_.`nested-module`.`nested-target`))) -// "neg" - check( -// "nested-module.doesntExist", -// Left( -// "Cannot resolve nested-module.doesntExist. Try `mill resolve nested-module._` to see what's available." -// ) -// ) -// } -// } + "backtickIdentifiers" - { + val check = ResolversTests.check(bactickIdentifiers) _ + "pos1" - check("up-target", Right(Set(_.`up-target`))) + "pos2" - check("a-down-target", Right(Set(_.`a-down-target`))) + "neg1" - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) + "neg2" - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) + "neg3" - check( + "up-target.doesntExist", + Left("Cannot resolve up-target.doesntExist. up-target resolves to a Task with no children.") + ) + "neg4" - check("", Left("Selector cannot be empty")) + "neg5" - check( + "invisible&", + Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.") + ) + "nested" - { + "pos" - check("nested-module.nested-target", Right(Set(_.`nested-module`.`nested-target`))) + "neg" - check( + "nested-module.doesntExist", + Left( + "Cannot resolve nested-module.doesntExist. Try `mill resolve nested-module._` to see what's available." + ) + ) + } + } "nested" - { - val check = MainTests.check(nestedModule) _ + val check = ResolversTests.check(nestedModule) _ "pos1" - check("single", Right(Set(_.single))) "pos2" - check("nested.single", Right(Set(_.nested.single))) "pos3" - check("classInstance.single", Right(Set(_.classInstance.single))) @@ -144,7 +144,7 @@ object MainTests extends TestSuite { ) } "doubleNested" - { - val check = MainTests.check(doubleNestedModule) _ + val check = ResolversTests.check(doubleNestedModule) _ "pos1" - check("single", Right(Set(_.single))) "pos2" - check("nested.single", Right(Set(_.nested.single))) "pos3" - check("nested.inner.single", Right(Set(_.nested.inner.single))) @@ -174,7 +174,7 @@ object MainTests extends TestSuite { "cross" - { "single" - { - val check = MainTests.check(singleCross) _ + val check = ResolversTests.check(singleCross) _ "pos1" - check("cross[210].suffix", Right(Set(_.cross("210").suffix))) "pos2" - check("cross[211].suffix", Right(Set(_.cross("211").suffix))) "neg1" - check( @@ -217,7 +217,7 @@ object MainTests extends TestSuite { ) } "double" - { - val check = MainTests.check(doubleCross) _ + val check = ResolversTests.check(doubleCross) _ "pos1" - check( "cross[210,jvm].suffix", Right(Set(_.cross("210", "jvm").suffix)) @@ -307,7 +307,7 @@ object MainTests extends TestSuite { } } "nested" - { - val check = MainTests.check(nestedCrosses) _ + val check = ResolversTests.check(nestedCrosses) _ "pos1" - check( "cross[210].cross2[js].suffix", Right(Set(_.cross("210").cross2("js").suffix)) @@ -355,7 +355,7 @@ object MainTests extends TestSuite { } "nestedCrossTaskModule" - { - val check = MainTests.checkSeq(nestedTaskCrosses) _ + val check = ResolversTests.checkSeq(nestedTaskCrosses) _ "pos1" - check( Seq("cross1[210].cross2[js].suffixCmd"), Right(Set(_.cross1("210").cross2("js").suffixCmd())) From 4b95cc4390bd5ed04b7ffdbf6f5e4035a0292544 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 10:49:48 +0800 Subject: [PATCH 15/57] all main.test passes --- main/core/src/mill/define/Module.scala | 8 +++++--- main/src/mill/main/MainModule.scala | 6 +++--- main/src/mill/main/Resolve.scala | 4 ++-- main/src/mill/main/RunScript.scala | 17 +---------------- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index c802bbc574e..2b66cb19e2c 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -45,13 +45,15 @@ object Module { def reflect( outer: Class[_], inner: Class[_], - filter: String => Boolean + filter: String => Boolean, + noParams: Boolean ): Seq[java.lang.reflect.Method] = { for { m <- outer.getMethods.sortBy(_.getName) n = decode(m.getName) if filter(n) && ParseArgs.isLegalIdentifier(n) && + (!noParams || m.getParameterCount == 0) && (m.getModifiers & Modifier.STATIC) == 0 && (m.getModifiers & Modifier.ABSTRACT) == 0 && inner.isAssignableFrom(m.getReturnType) @@ -65,7 +67,7 @@ object Module { outer: Class[_], filter: String => Boolean = Function.const(true) ): Seq[java.lang.reflect.Member] = { - reflect(outer, classOf[Object], filter) ++ + reflect(outer, classOf[Object], filter, noParams = true) ++ outer .getClasses .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) @@ -97,7 +99,7 @@ object Module { lazy val millModuleLine: Int = outer.millOuterCtx.lineNum def reflect[T: ClassTag](filter: String => Boolean): Seq[T] = { - Module.reflect(outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter) + Module.reflect(outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter, noParams = true) .map(_.invoke(outer).asInstanceOf[T]) } diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index f09823225f6..42f1b5f51e4 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -315,7 +315,7 @@ trait MainModule extends mill.Module { case _ => false } - val pathsToRemove = Right(os.list(rootDir).filterNot(keepPath)) + val pathsToRemove = if (targets.isEmpty) Right(os.list(rootDir).filterNot(keepPath)) else @@ -326,8 +326,8 @@ trait MainModule extends mill.Module { SelectMode.Multi ).map { ts => ts.flatMap { segments => - val evPpaths = EvaluatorPaths.resolveDestPaths(rootDir, segments) - val paths = Seq(evPpaths.dest, evPpaths.meta, evPpaths.log) + val evPaths = EvaluatorPaths.resolveDestPaths(rootDir, segments) + val paths = Seq(evPaths.dest, evPaths.meta, evPaths.log) val potentialModulePath = rootDir / EvaluatorPaths.makeSegmentStrings(segments) if (os.exists(potentialModulePath)) { // this is either because of some pre-Mill-0.10 files lying around diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index a652227ea28..93085222504 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -133,11 +133,11 @@ object Resolve { } val targets = Module - .reflect(obj.getClass, classOf[Target[_]], namePred) + .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) .map(m => Segment.Label(decode(m.getName)) -> Right(Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]))) val commands = Module - .reflect(obj.getClass, classOf[Command[_]], namePred) + .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) .map(m => decode(m.getName)) .map(name => Segment.Label(name) -> Right(Resolved.Command( diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index 27f1e6d67d9..3509b138c87 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -46,9 +46,8 @@ object RunScript { (selectors, args) = parsed taskss <- { val selected = selectors.map { case (scopedSel, sel) => - for (res <- prepareResolve(evaluator, scopedSel, sel)) + for (rootModule <- resolveRootModule(evaluator, scopedSel)) yield { - val (rootModule, crossSelectors) = res try { // We inject the `evaluator.rootModule` into the TargetScopt, rather @@ -93,20 +92,6 @@ object RunScript { } } - def prepareResolve[T]( - evaluator: Evaluator, - scopedSel: Option[Segments], - sel: Segments - ): Either[String, (BaseModule, Seq[List[String]])] = { - for (rootModule <- resolveRootModule(evaluator, scopedSel)) - yield { - val crossSelectors = sel.value.map { - case Segment.Cross(x) => x.toList.map(_.toString) - case _ => Nil - } - (rootModule, crossSelectors) - } - } def evaluateTasks[T]( evaluator: Evaluator, From 184dfcb48a3b2b0dd3f51eee09bbb5f285bebb95 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 11:01:10 +0800 Subject: [PATCH 16/57] break out ResolveNonEmpty --- main/core/src/mill/define/Module.scala | 4 +- main/core/src/mill/define/Segment.scala | 4 +- main/core/src/mill/eval/Evaluator.scala | 4 +- main/src/mill/main/MainModule.scala | 17 +- main/src/mill/main/Resolve.scala | 352 +++++++++---------- main/src/mill/main/ResolveCore.scala | 218 ++++++++++++ main/src/mill/main/ResolveNonEmpty.scala | 107 ++++++ main/src/mill/main/Resolvers.scala | 151 -------- main/src/mill/main/RunScript.scala | 85 +---- main/src/mill/main/TokenReaders.scala | 3 +- main/test/src/mill/main/ResolversTests.scala | 2 +- 11 files changed, 505 insertions(+), 442 deletions(-) create mode 100644 main/src/mill/main/ResolveCore.scala create mode 100644 main/src/mill/main/ResolveNonEmpty.scala delete mode 100644 main/src/mill/main/Resolvers.scala diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 2b66cb19e2c..5572513382a 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -118,8 +118,8 @@ object Module { .getClasses .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) .flatMap { c => - c.getSimpleName match{ - case s"$name$$" if filter(name)=> + c.getSimpleName match { + case s"$name$$" if filter(name) => c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T]) case _ => None } diff --git a/main/core/src/mill/define/Segment.scala b/main/core/src/mill/define/Segment.scala index 9477feab47a..d9e33c13db2 100644 --- a/main/core/src/mill/define/Segment.scala +++ b/main/core/src/mill/define/Segment.scala @@ -10,10 +10,10 @@ sealed trait Segment { } object Segment { - final case class Label(value: String) extends Segment{ + final case class Label(value: String) extends Segment { def render = s".$value" } - final case class Cross(value: Seq[String]) extends Segment{ + final case class Cross(value: Seq[String]) extends Segment { def render = "[" + value.mkString(",") + "]" } } diff --git a/main/core/src/mill/eval/Evaluator.scala b/main/core/src/mill/eval/Evaluator.scala index 887b3e7a73f..39bad575c4d 100644 --- a/main/core/src/mill/eval/Evaluator.scala +++ b/main/core/src/mill/eval/Evaluator.scala @@ -822,8 +822,8 @@ object Evaluator { val Segment.Label(tName) = segments.value.last Segments( segments.value.init ++ - Seq(Segment.Label(tName + ".super")) ++ - t.ctx.enclosing.split("[.# ]").map(Segment.Label) + Seq(Segment.Label(tName + ".super")) ++ + t.ctx.enclosing.split("[.# ]").map(Segment.Label) ) } ) diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 42f1b5f51e4..af91aeff217 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -21,7 +21,7 @@ object MainModule { targets: Seq[String], selectMode: SelectMode )(f: List[NamedTask[Any]] => T): Result[T] = { - RunScript.resolveTasks(ResolveTasks, evaluator, targets, selectMode) match { + ResolveTasks.resolveTasks(evaluator, targets, selectMode) match { case Left(err) => Result.Failure(err) case Right(tasks) => Result.Success(f(tasks)) } @@ -74,8 +74,7 @@ trait MainModule extends mill.Module { * Resolves a mill query string and prints out the tasks it resolves to. */ def resolve(evaluator: Evaluator, targets: String*): Command[List[String]] = T.command { - val resolved: Either[String, List[String]] = RunScript.resolveTasks( - ResolveMetadata, + val resolved: Either[String, List[String]] = ResolveMetadata.resolveTasks( evaluator, targets, SelectMode.Multi @@ -105,8 +104,7 @@ trait MainModule extends mill.Module { } private def plan0(evaluator: Evaluator, targets: Seq[String]) = { - RunScript.resolveTasks( - ResolveTasks, + ResolveTasks.resolveTasks( evaluator, targets, SelectMode.Multi @@ -125,8 +123,7 @@ trait MainModule extends mill.Module { * chosen is arbitrary. */ def path(evaluator: Evaluator, src: String, dest: String): Command[List[String]] = T.command { - val resolved = RunScript.resolveTasks( - ResolveTasks, + val resolved = ResolveTasks.resolveTasks( evaluator, List(src, dest), SelectMode.Multi @@ -319,8 +316,7 @@ trait MainModule extends mill.Module { if (targets.isEmpty) Right(os.list(rootDir).filterNot(keepPath)) else - RunScript.resolveTasks( - mill.main.ResolveSegments, + mill.main.ResolveSegments.resolveTasks( evaluator, targets, SelectMode.Multi @@ -411,8 +407,7 @@ trait MainModule extends mill.Module { out.take() } - RunScript.resolveTasks( - ResolveTasks, + ResolveTasks.resolveTasks( evaluator, targets, SelectMode.Multi diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 93085222504..8fe2682d996 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -1,213 +1,187 @@ package mill.main -import mainargs.{MainData, TokenGrouping} -import mill.define._ +import mill.define.ParseArgs.TargetsWithParams +import mill.define.{ + BaseModule, + Discover, + ExternalModule, + NamedTask, + ParseArgs, + Segment, + Segments, + SelectMode, + TaskModule +} +import mill.eval.Evaluator +import mill.main.ResolveCore.Resolved import mill.util.EitherOps -import scala.reflect.NameTransformer.decode - -import scala.collection.immutable -/** - * Takes a single list of segments, without braces but including wildcards, and - * resolves all possible modules, targets or commands that the segments could - * resolve to. - * - * Returns a [[Result]], either containing a [[Success]] containing the - * [[Resolved]] set, [[NotFound]] if it couldn't find anything with some - * metadata about what it was looking for, or [[Error]] if something blew up. - */ -object Resolve { - - sealed trait Resolved{ - def segments: Segments - } - - object Resolved { - case class Module(value: mill.define.Module) extends Resolved{ - def segments = value.millModuleSegments - } - case class Target(value: mill.define.Target[_]) extends Resolved{ - def segments = value.ctx.segments - } - case class Command(value: () => Either[String, mill.define.Command[_]]) - extends Resolved{ - def segments = value().right.get.ctx.segments - } +object ResolveSegments extends Resolve[Segments] { + def resolveNonEmpty( + selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ) = { + ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments) } } +} - sealed trait Result - case class Success(value: Set[Resolved]) extends Result{ - assert(value.nonEmpty) +object ResolveMetadata extends Resolve[String] { + def resolveNonEmpty( + selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ) = { + ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments.render) } } - sealed trait Failed extends Result - case class NotFound(deepest: Segments, - found: Set[Resolved], - next: Segment, - possibleNexts: Set[Segment]) extends Failed - case class Error(msg: String) extends Failed - +} +object ResolveTasks extends Resolve[NamedTask[Any]] { + def resolveNonEmpty( + selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ) = { + ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).flatMap { value => + val taskList: Set[Either[String, NamedTask[_]]] = value.collect { + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + case Resolved.Module(value: TaskModule) => + ResolveCore.resolveDirectChildren( + value, + Some(value.defaultCommandName()), + discover, + args + ).values.head.flatMap { + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + } + } - def resolve( - remainingSelector: List[Segment], - current: Resolved, - discover: Discover[_], - args: Seq[String], - revSelectorsSoFar0: List[Segment] - ): Result = remainingSelector match { - case Nil => Success(Set(current)) - case head :: tail => - val revSelectorsSoFar = head :: revSelectorsSoFar0 - def recurse(searchModules: Set[Resolved]): Result = { - val (failures, successesLists) = searchModules - .map(resolve(tail, _, discover, args, revSelectorsSoFar)) - .partitionMap{case s: Success => Right(s.value); case f: Failed => Left(f)} + if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) + else Left(s"Cannot find default task to evaluate for module ${Segments(selector).render}") + } - val (errors, notFounds) = failures.partitionMap{ - case s: NotFound => Right(s) - case s: Error => Left(s.msg) - } + } +} - if (errors.nonEmpty) Error(errors.mkString("\n")) - else if (successesLists.flatten.nonEmpty) Success(successesLists.flatten) - else notFounds.size match{ - case 1 => notFounds.head - case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) +trait Resolve[T] { + def resolveNonEmpty( + selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ): Either[String, Set[T]] + + def resolveTasks( + evaluator: Evaluator, + scriptArgs: Seq[String], + selectMode: SelectMode + ): Either[String, List[T]] = { + val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) + val resolvedGroups = parsedGroups.flatMap { groups => + val resolved = groups.map { case (selectors, args) => + val selected = selectors.map { case (scopedSel, sel) => + for (rootModule <- resolveRootModule(evaluator, scopedSel)) + yield try { + // We inject the `evaluator.rootModule` into the TargetScopt, rather + // than the `rootModule`, because even if you are running an external + // module we still want you to be able to resolve targets from your + // main build. Resolving targets from external builds as CLI arguments + // is not currently supported + mill.eval.Evaluator.currentEvaluator.set(evaluator) + resolveNonEmpty( + sel.value.toList, + rootModule, + rootModule.millDiscover, + args + ) + } finally { + mill.eval.Evaluator.currentEvaluator.set(null) + } } + for { + taskss <- EitherOps.sequence(selected).map(_.toList) + res <- EitherOps.sequence(taskss) + } yield res.flatten } + EitherOps.sequence(resolved) + } + resolvedGroups.map(_.flatten.toList) + } - (head, current) match { - case (Segment.Label(singleLabel), Resolved.Module(obj)) => - EitherOps.sequence( - singleLabel match { - case "__" => - obj - .millInternal - .modules - .flatMap(m => - Seq(Right(Resolved.Module(m))) ++ - resolveDirectChildren(m, None, discover, args).values - ) - case "_" => resolveDirectChildren(obj, None, discover, args).values - case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args).values + def resolveRootModule(evaluator: Evaluator, scopedSel: Option[Segments]) = { + scopedSel match { + case None => Right(evaluator.rootModule) + case Some(scoping) => + for { + moduleCls <- + try Right(evaluator.rootModule.getClass.getClassLoader.loadClass(scoping.render + "$")) + catch { + case e: ClassNotFoundException => + Left("Cannot resolve external module " + scoping.render) } - ) match{ - case Left(err) => Error(err) - case Right(v) => recurse(v.toSet) + rootModule <- moduleCls.getField("MODULE$").get(moduleCls) match { + case rootModule: ExternalModule => Right(rootModule) + case _ => Left("Class " + scoping.render + " is not an external module") } - - case (Segment.Cross(cross), Resolved.Module(c: Cross[_])) => - val searchModules: Seq[Module] = - if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v - else if (cross.contains("_")) { - for { - (segments, v) <- c.segmentsToModules.toList - if segments.length == cross.length - if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } - } yield v - } else c.segmentsToModules.get(cross.toList).toSeq - - recurse(searchModules.map(m => Resolved.Module(m)).toSet) - - case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) - } + } yield rootModule + } } - - def resolveDirectChildren( - obj: Module, - nameOpt: Option[String] = None, - discover: Discover[_], - args: Seq[String] - ): Map[Segment, Either[String, Resolved]] = { - def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n) - - val modules = obj - .millInternal - .reflectNestedObjects[Module](namePred) - .map(t => Segment.Label(t.millModuleSegments.parts.last) -> Right(Resolved.Module(t))) - - val crosses = obj match { - case c: Cross[_] if nameOpt.isEmpty => c.segmentsToModules.map{case (k, v) => Segment.Cross(k) -> Right(Resolved.Module(v))} - case _ => Nil + def resolveTasks[R]( + resolver: mill.main.Resolver[R], + evaluator: Evaluator, + scriptArgs: Seq[String], + selectMode: SelectMode + ): Either[String, List[R]] = { + val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) + val resolvedGroups = parsedGroups.flatMap { groups => + val resolved = groups.map { parsed: TargetsWithParams => + resolveTasks(resolver, evaluator, Right(parsed)) + } + EitherOps.sequence(resolved) } - - val targets = Module - .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) - .map(m => Segment.Label(decode(m.getName)) -> Right(Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]))) - - val commands = Module - .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) - .map(m => decode(m.getName)) - .map(name => - Segment.Label(name) -> Right(Resolved.Command( - () => - invokeCommand( - obj, - name, - discover.asInstanceOf[Discover[Module]], - args - ).head - )) - ) - - (modules ++ crosses ++ targets ++ commands).toMap + resolvedGroups.map(_.flatten.toList) } - def notFoundResult(revSelectorsSoFar0: List[Segment], - current: Resolved, - next: Segment, - discover: Discover[_], - args: Seq[String]) = - NotFound( - Segments(revSelectorsSoFar0.reverse), - Set(current), - next, - current match { - case Resolved.Module(obj) => - resolveDirectChildren(obj, None, discover, args).keySet - - case _ => Set() + private def resolveTasks[R]( + resolver: mill.main.Resolver[R], + evaluator: Evaluator, + targetsWithParams: Either[String, TargetsWithParams] + ): Either[String, List[R]] = { + for { + parsed <- targetsWithParams + (selectors, args) = parsed + taskss <- { + val selected = selectors.map { case (scopedSel, sel) => + for (rootModule <- resolveRootModule(evaluator, scopedSel)) + yield { + + try { + // We inject the `evaluator.rootModule` into the TargetScopt, rather + // than the `rootModule`, because even if you are running an external + // module we still want you to be able to resolve targets from your + // main build. Resolving targets from external builds as CLI arguments + // is not currently supported + mill.eval.Evaluator.currentEvaluator.set(evaluator) + resolver.resolveNonEmpty( + sel.value.toList, + rootModule, + rootModule.millDiscover, + args + ) + } finally { + mill.eval.Evaluator.currentEvaluator.set(null) + } + } + } + EitherOps.sequence(selected).map(_.toList) } - ) - - def invokeCommand( - target: Module, - name: String, - discover: Discover[Module], - rest: Seq[String] - ): immutable.Iterable[Either[String, Command[_]]] = for { - (cls, entryPoints) <- discover.value - if cls.isAssignableFrom(target.getClass) - ep <- entryPoints - if ep._2.name == name - } yield { - mainargs.TokenGrouping.groupArgs( - rest, - ep._2.argSigs0, - allowPositional = true, - allowRepeats = false, - allowLeftover = ep._2.leftoverArgSig.nonEmpty - ).flatMap { grouped => - mainargs.Invoker.invoke( - target, - ep._2.asInstanceOf[MainData[_, Any]], - grouped.asInstanceOf[TokenGrouping[Any]] - ) - } match { - case mainargs.Result.Success(v: Command[_]) => Right(v) - case f: mainargs.Result.Failure => - Left( - mainargs.Renderer.renderResult( - ep._2, - f, - totalWidth = 100, - printHelpOnError = true, - docsOnNewLine = false, - customName = None, - customDoc = None - ) - ) - } + res <- EitherOps.sequence(taskss) + } yield res.flatten } } diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala new file mode 100644 index 00000000000..2e7c08c93a9 --- /dev/null +++ b/main/src/mill/main/ResolveCore.scala @@ -0,0 +1,218 @@ +package mill.main + +import mainargs.{MainData, TokenGrouping} +import mill.define._ +import mill.util.EitherOps +import scala.reflect.NameTransformer.decode + +import scala.collection.immutable + +/** + * Takes a single list of segments, without braces but including wildcards, and + * resolves all possible modules, targets or commands that the segments could + * resolve to. + * + * Returns a [[Result]], either containing a [[Success]] containing the + * [[Resolved]] set, [[NotFound]] if it couldn't find anything with some + * metadata about what it was looking for, or [[Error]] if something blew up. + */ +object ResolveCore { + + sealed trait Resolved { + def segments: Segments + } + + object Resolved { + case class Module(value: mill.define.Module) extends Resolved { + def segments = value.millModuleSegments + } + case class Target(value: mill.define.Target[_]) extends Resolved { + def segments = value.ctx.segments + } + case class Command(value: () => Either[String, mill.define.Command[_]]) + extends Resolved { + def segments = value().right.get.ctx.segments + } + } + + sealed trait Result + case class Success(value: Set[Resolved]) extends Result { + assert(value.nonEmpty) + } + sealed trait Failed extends Result + case class NotFound( + deepest: Segments, + found: Set[Resolved], + next: Segment, + possibleNexts: Set[Segment] + ) extends Failed + case class Error(msg: String) extends Failed + + def resolve( + remainingSelector: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String], + revSelectorsSoFar0: List[Segment] + ): Result = remainingSelector match { + case Nil => Success(Set(current)) + case head :: tail => + val revSelectorsSoFar = head :: revSelectorsSoFar0 + def recurse(searchModules: Set[Resolved]): Result = { + val (failures, successesLists) = searchModules + .map(resolve(tail, _, discover, args, revSelectorsSoFar)) + .partitionMap { case s: Success => Right(s.value); case f: Failed => Left(f) } + + val (errors, notFounds) = failures.partitionMap { + case s: NotFound => Right(s) + case s: Error => Left(s.msg) + } + + if (errors.nonEmpty) Error(errors.mkString("\n")) + else if (successesLists.flatten.nonEmpty) Success(successesLists.flatten) + else notFounds.size match { + case 1 => notFounds.head + case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) + } + } + + (head, current) match { + case (Segment.Label(singleLabel), Resolved.Module(obj)) => + EitherOps.sequence( + singleLabel match { + case "__" => + obj + .millInternal + .modules + .flatMap(m => + Seq(Right(Resolved.Module(m))) ++ + resolveDirectChildren(m, None, discover, args).values + ) + case "_" => resolveDirectChildren(obj, None, discover, args).values + case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args).values + } + ) match { + case Left(err) => Error(err) + case Right(v) => recurse(v.toSet) + } + + case (Segment.Cross(cross), Resolved.Module(c: Cross[_])) => + val searchModules: Seq[Module] = + if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v + else if (cross.contains("_")) { + for { + (segments, v) <- c.segmentsToModules.toList + if segments.length == cross.length + if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } + } yield v + } else c.segmentsToModules.get(cross.toList).toSeq + + recurse(searchModules.map(m => Resolved.Module(m)).toSet) + + case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) + } + } + + def resolveDirectChildren( + obj: Module, + nameOpt: Option[String] = None, + discover: Discover[_], + args: Seq[String] + ): Map[Segment, Either[String, Resolved]] = { + def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n) + + val modules = obj + .millInternal + .reflectNestedObjects[Module](namePred) + .map(t => Segment.Label(t.millModuleSegments.parts.last) -> Right(Resolved.Module(t))) + + val crosses = obj match { + case c: Cross[_] if nameOpt.isEmpty => + c.segmentsToModules.map { case (k, v) => Segment.Cross(k) -> Right(Resolved.Module(v)) } + case _ => Nil + } + + val targets = Module + .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) + .map(m => + Segment.Label(decode(m.getName)) -> Right( + Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]) + ) + ) + + val commands = Module + .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) + .map(m => decode(m.getName)) + .map(name => + Segment.Label(name) -> Right(Resolved.Command(() => + invokeCommand( + obj, + name, + discover.asInstanceOf[Discover[Module]], + args + ).head + )) + ) + + (modules ++ crosses ++ targets ++ commands).toMap + } + + def notFoundResult( + revSelectorsSoFar0: List[Segment], + current: Resolved, + next: Segment, + discover: Discover[_], + args: Seq[String] + ) = + NotFound( + Segments(revSelectorsSoFar0.reverse), + Set(current), + next, + current match { + case Resolved.Module(obj) => + resolveDirectChildren(obj, None, discover, args).keySet + + case _ => Set() + } + ) + + def invokeCommand( + target: Module, + name: String, + discover: Discover[Module], + rest: Seq[String] + ): immutable.Iterable[Either[String, Command[_]]] = for { + (cls, entryPoints) <- discover.value + if cls.isAssignableFrom(target.getClass) + ep <- entryPoints + if ep._2.name == name + } yield { + mainargs.TokenGrouping.groupArgs( + rest, + ep._2.argSigs0, + allowPositional = true, + allowRepeats = false, + allowLeftover = ep._2.leftoverArgSig.nonEmpty + ).flatMap { grouped => + mainargs.Invoker.invoke( + target, + ep._2.asInstanceOf[MainData[_, Any]], + grouped.asInstanceOf[TokenGrouping[Any]] + ) + } match { + case mainargs.Result.Success(v: Command[_]) => Right(v) + case f: mainargs.Result.Failure => + Left( + mainargs.Renderer.renderResult( + ep._2, + f, + totalWidth = 100, + printHelpOnError = true, + docsOnNewLine = false, + customName = None, + customDoc = None + ) + ) + } + } +} diff --git a/main/src/mill/main/ResolveNonEmpty.scala b/main/src/mill/main/ResolveNonEmpty.scala new file mode 100644 index 00000000000..374ba4c41a5 --- /dev/null +++ b/main/src/mill/main/ResolveNonEmpty.scala @@ -0,0 +1,107 @@ +package mill.main + +import mill.define.{BaseModule, Discover, Segment, Segments} +import mill.main.ResolveCore.Resolved + +/** + * Wraps [[ResolveCore]] to report error messages if nothing was resolved + */ +object ResolveNonEmpty{ + def resolveNonEmpty( + selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ): Either[String, Set[Resolved]] = { + ResolveCore.resolve( + selector, + ResolveCore.Resolved.Module(current), + discover, + args, + Nil + ) match { + case ResolveCore.Success(value) => Right(value) + + case ResolveCore.NotFound(segments, found, next, possibleNexts) => + val errorMsg = found.head match { + case s: Resolved.Module => + next match { + case Segment.Label(s) => + val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } + + errorMsgLabel(s, possibleStrings, segments, Segments(selector)) + case Segment.Cross(keys) => + val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } + errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) + } + case x => + unableToResolve((segments ++ Seq(next)).render) + + s" ${segments.render} resolves to a Task with no children." + } + + Left(errorMsg) + + case ResolveCore.Error(value) => Left(value) + } + } + + + def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." + + def hintList(revSelectorsSoFar: Seq[Segment]) = { + val search = Segments(revSelectorsSoFar).render + s" Try `mill resolve $search` to see what's available." + } + + def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { + hintList(revSelectorsSoFar :+ Segment.Label("_")) + } + + def findMostSimilar(given: String, options: Set[String]): Option[String] = { + options + .map { option => (option, LevenshteinDistance.editDistance(given, option)) } + .filter(_._2 < 3) + .minByOption(_._2) + .map(_._1) + } + + def errorMsgLabel( + given: String, + possibleMembers: Set[String], + prefixSegments: Segments, + fullSegments: Segments + ) = { + val suggestion = findMostSimilar(given, possibleMembers) match { + case None => hintListLabel(prefixSegments.value) + case Some(similar) => + " Did you mean " + + (prefixSegments ++ Seq(Segment.Label(similar))).render + + "?" + } + + val msg = unableToResolve(fullSegments.render) + suggestion + + msg + } + + def errorMsgCross( + givenKeys: Seq[String], + possibleCrossKeys: Set[Seq[String]], + prefixSegments: Segments, + fullSegments: Segments + ) = { + + val suggestion = findMostSimilar( + givenKeys.mkString(","), + possibleCrossKeys.map(_.mkString(",")) + ) match { + case None => hintListLabel(prefixSegments.value) + case Some(similar) => + " Did you mean " + + (prefixSegments ++ Seq(Segment.Cross(similar.split(',')))).render + + "?" + } + + unableToResolve(fullSegments.render) + suggestion + } +} diff --git a/main/src/mill/main/Resolvers.scala b/main/src/mill/main/Resolvers.scala deleted file mode 100644 index 95e4bf35c30..00000000000 --- a/main/src/mill/main/Resolvers.scala +++ /dev/null @@ -1,151 +0,0 @@ -package mill.main - -import mill.define.{BaseModule, Discover, NamedTask, Segment, Segments, TaskModule} -import mill.main.Resolve.Resolved -import mill.util.EitherOps - -import scala.util.Either - - -object ResolveSegments extends Resolver[Segments] { - def resolve(selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String]) = { - resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments) } - } -} - -object ResolveMetadata extends Resolver[String] { - def resolve(selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String]) = { - resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments.render) } - } -} - -object ResolveTasks extends Resolver[NamedTask[Any]]{ - def resolve(selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String]) = { - resolveNonEmpty(selector, current, discover, args).flatMap{ value => - val taskList: Set[Either[String, NamedTask[_]]] = value.collect { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - case Resolved.Module(value: TaskModule) => - Resolve.resolveDirectChildren(value, Some(value.defaultCommandName()), discover, args).values.head.flatMap { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - } - } - - if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) - else Left(s"Cannot find default task to evaluate for module ${Segments(selector).render}") - } - - } -} - - -trait Resolver[T]{ - def resolve(selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String]): Either[String, Set[T]] - - def resolveNonEmpty(selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String]): Either[String, Set[Resolved]] = { - Resolve.resolve( - selector, - Resolve.Resolved.Module(current), - discover, - args, - Nil - ) match { - case Resolve.Success(value) => Right(value) - - case Resolve.NotFound(segments, found, next, possibleNexts) => - val errorMsg = found.head match { - case s: Resolved.Module => - next match { - case Segment.Label(s) => - val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } - - errorMsgLabel(s, possibleStrings, segments, Segments(selector)) - case Segment.Cross(keys) => - val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } - errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) - } - case x => - unableToResolve((segments ++ Seq(next)).render) + - s" ${segments.render} resolves to a Task with no children." - } - - Left(errorMsg) - - case Resolve.Error(value) => Left(value) - } - } - - - def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." - - - def hintList(revSelectorsSoFar: Seq[Segment]) = { - val search = Segments(revSelectorsSoFar).render - s" Try `mill resolve $search` to see what's available." - } - - def hintListLabel(revSelectorsSoFar: Seq[Segment]) = { - hintList(revSelectorsSoFar :+ Segment.Label("_")) - } - - def findMostSimilar(given: String, - options: Set[String]): Option[String] = { - options - .map { option => (option, LevenshteinDistance.editDistance(given, option)) } - .filter(_._2 < 3) - .minByOption(_._2) - .map(_._1) - } - - def errorMsgLabel(given: String, - possibleMembers: Set[String], - prefixSegments: Segments, - fullSegments: Segments) = { - val suggestion = findMostSimilar(given, possibleMembers) match { - case None => hintListLabel(prefixSegments.value) - case Some(similar) => - " Did you mean " + - (prefixSegments ++ Seq(Segment.Label(similar))).render + - "?" - } - - val msg = unableToResolve(fullSegments.render) + suggestion - - msg - } - - def errorMsgCross(givenKeys: Seq[String], - possibleCrossKeys: Set[Seq[String]], - prefixSegments: Segments, - fullSegments: Segments) = { - - val suggestion = findMostSimilar( - givenKeys.mkString(","), - possibleCrossKeys.map(_.mkString(",")) - ) match { - case None => hintListLabel(prefixSegments.value) - case Some(similar) => - " Did you mean " + - (prefixSegments ++ Seq(Segment.Cross(similar.split(',')))).render + - "?" - } - - unableToResolve(fullSegments.render) + suggestion - } -} diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index 3509b138c87..da513b8f035 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -10,95 +10,16 @@ import mill.api.Strict.Agg import scala.reflect.ClassTag import mill.define.ParseArgs.TargetsWithParams -/** - * Custom version of ammonite.main.Scripts, letting us run the build.sc script - * directly without going through Ammonite's main-method/argument-parsing - * subsystem - */ object RunScript { type TaskName = String - - def resolveTasks[T, R: ClassTag]( - resolver: mill.main.Resolver[R], - evaluator: Evaluator, - scriptArgs: Seq[String], - selectMode: SelectMode - ): Either[String, List[R]] = { - val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) - val resolvedGroups = parsedGroups.flatMap { groups => - val resolved = groups.map { parsed: TargetsWithParams => - resolveTasks(resolver, evaluator, Right(parsed)) - } - EitherOps.sequence(resolved) - } - resolvedGroups.map(_.flatten.toList) - } - - private def resolveTasks[T, R: ClassTag]( - resolver: mill.main.Resolver[R], - evaluator: Evaluator, - targetsWithParams: Either[String, TargetsWithParams] - ): Either[String, List[R]] = { - for { - parsed <- targetsWithParams - (selectors, args) = parsed - taskss <- { - val selected = selectors.map { case (scopedSel, sel) => - for (rootModule <- resolveRootModule(evaluator, scopedSel)) - yield { - - try { - // We inject the `evaluator.rootModule` into the TargetScopt, rather - // than the `rootModule`, because even if you are running an external - // module we still want you to be able to resolve targets from your - // main build. Resolving targets from external builds as CLI arguments - // is not currently supported - mill.eval.Evaluator.currentEvaluator.set(evaluator) - resolver.resolve( - sel.value.toList, - rootModule, - rootModule.millDiscover, - args - ) - } finally { - mill.eval.Evaluator.currentEvaluator.set(null) - } - } - } - EitherOps.sequence(selected).map(_.toList) - } - res <- EitherOps.sequence(taskss) - } yield res.flatten - } - - def resolveRootModule[T](evaluator: Evaluator, scopedSel: Option[Segments]) = { - scopedSel match { - case None => Right(evaluator.rootModule) - case Some(scoping) => - for { - moduleCls <- - try Right(evaluator.rootModule.getClass.getClassLoader.loadClass(scoping.render + "$")) - catch { - case e: ClassNotFoundException => - Left("Cannot resolve external module " + scoping.render) - } - rootModule <- moduleCls.getField("MODULE$").get(moduleCls) match { - case rootModule: ExternalModule => Right(rootModule) - case _ => Left("Class " + scoping.render + " is not an external module") - } - } yield rootModule - } - } - - - def evaluateTasks[T]( + def evaluateTasks( evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[ujson.Value])]])] = { - for (targets <- resolveTasks(ResolveTasks, evaluator, scriptArgs, selectMode)) + for (targets <- ResolveTasks.resolveTasks(evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluate(evaluator, Agg.from(targets.distinct)) @@ -117,7 +38,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[(TaskName, ujson.Value)])]])] = { - for (targets <- resolveTasks(ResolveTasks, evaluator, scriptArgs, selectMode)) + for (targets <- ResolveTasks.resolveTasks(evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluateNamed(evaluator, Agg.from(targets.distinct)) diff --git a/main/src/mill/main/TokenReaders.scala b/main/src/mill/main/TokenReaders.scala index 56db4d7b827..78a127869be 100644 --- a/main/src/mill/main/TokenReaders.scala +++ b/main/src/mill/main/TokenReaders.scala @@ -11,8 +11,7 @@ object Tasks { extends mainargs.TokensReader[Tasks[T]]( shortName = "", read = s => - RunScript.resolveTasks( - ResolveTasks, + ResolveTasks.resolveTasks( Evaluator.currentEvaluator.get, s, SelectMode.Single diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index bc3991c0001..7193603ea19 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -18,7 +18,7 @@ object ResolversTests extends TestSuite { val expected = expected0.map(_.map(_(module))) val resolved = for { selectors <- mill.define.ParseArgs(selectorStrings, SelectMode.Single).map(_.head._1.head) - task <- mill.main.ResolveTasks.resolve( + task <- mill.main.ResolveTasks.resolveNonEmpty( selectors._2.value.toList, module, module.millDiscover, From c07f8b097b3a5de9845fdbc9ddc3c0fd9adb17ca Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 11:02:13 +0800 Subject: [PATCH 17/57] fix-compile --- main/src/mill/main/Resolve.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 8fe2682d996..c4363a8b89d 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -133,7 +133,7 @@ trait Resolve[T] { } def resolveTasks[R]( - resolver: mill.main.Resolver[R], + resolver: mill.main.Resolve[R], evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode @@ -149,7 +149,7 @@ trait Resolve[T] { } private def resolveTasks[R]( - resolver: mill.main.Resolver[R], + resolver: mill.main.Resolve[R], evaluator: Evaluator, targetsWithParams: Either[String, TargetsWithParams] ): Either[String, List[R]] = { From fa085b863e034ddfe2b401c1e611d1c1228c5982 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 11:03:43 +0800 Subject: [PATCH 18/57] scalafmt --- main/src/mill/main/Resolve.scala | 42 ++++++++-------- main/src/mill/main/ResolveNonEmpty.scala | 61 +++++++++++------------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index c4363a8b89d..a6cc684e9b7 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -23,7 +23,9 @@ object ResolveSegments extends Resolve[Segments] { discover: Discover[_], args: Seq[String] ) = { - ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments) } + ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => + value.map(_.segments) + } } } @@ -34,7 +36,9 @@ object ResolveMetadata extends Resolve[String] { discover: Discover[_], args: Seq[String] ) = { - ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => value.map(_.segments.render) } + ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => + value.map(_.segments.render) + } } } @@ -83,25 +87,25 @@ trait Resolve[T] { ): Either[String, List[T]] = { val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) val resolvedGroups = parsedGroups.flatMap { groups => - val resolved = groups.map { case (selectors, args) => + val resolved = groups.map { case (selectors, args) => val selected = selectors.map { case (scopedSel, sel) => for (rootModule <- resolveRootModule(evaluator, scopedSel)) - yield try { - // We inject the `evaluator.rootModule` into the TargetScopt, rather - // than the `rootModule`, because even if you are running an external - // module we still want you to be able to resolve targets from your - // main build. Resolving targets from external builds as CLI arguments - // is not currently supported - mill.eval.Evaluator.currentEvaluator.set(evaluator) - resolveNonEmpty( - sel.value.toList, - rootModule, - rootModule.millDiscover, - args - ) - } finally { - mill.eval.Evaluator.currentEvaluator.set(null) - } + yield try { + // We inject the `evaluator.rootModule` into the TargetScopt, rather + // than the `rootModule`, because even if you are running an external + // module we still want you to be able to resolve targets from your + // main build. Resolving targets from external builds as CLI arguments + // is not currently supported + mill.eval.Evaluator.currentEvaluator.set(evaluator) + resolveNonEmpty( + sel.value.toList, + rootModule, + rootModule.millDiscover, + args + ) + } finally { + mill.eval.Evaluator.currentEvaluator.set(null) + } } for { taskss <- EitherOps.sequence(selected).map(_.toList) diff --git a/main/src/mill/main/ResolveNonEmpty.scala b/main/src/mill/main/ResolveNonEmpty.scala index 374ba4c41a5..11267327816 100644 --- a/main/src/mill/main/ResolveNonEmpty.scala +++ b/main/src/mill/main/ResolveNonEmpty.scala @@ -6,13 +6,13 @@ import mill.main.ResolveCore.Resolved /** * Wraps [[ResolveCore]] to report error messages if nothing was resolved */ -object ResolveNonEmpty{ +object ResolveNonEmpty { def resolveNonEmpty( - selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String] - ): Either[String, Set[Resolved]] = { + selector: List[Segment], + current: BaseModule, + discover: Discover[_], + args: Seq[String] + ): Either[String, Set[Resolved]] = { ResolveCore.resolve( selector, ResolveCore.Resolved.Module(current), @@ -21,22 +21,20 @@ object ResolveNonEmpty{ Nil ) match { case ResolveCore.Success(value) => Right(value) - case ResolveCore.NotFound(segments, found, next, possibleNexts) => - val errorMsg = found.head match { - case s: Resolved.Module => - next match { - case Segment.Label(s) => - val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } - - errorMsgLabel(s, possibleStrings, segments, Segments(selector)) - case Segment.Cross(keys) => - val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } - errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) - } - case x => - unableToResolve((segments ++ Seq(next)).render) + - s" ${segments.render} resolves to a Task with no children." + val errorMsg = if (found.head.isInstanceOf[Resolved.Module]) { + next match { + case Segment.Label(s) => + val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } + + errorMsgLabel(s, possibleStrings, segments, Segments(selector)) + case Segment.Cross(keys) => + val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } + errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) + } + } else { + unableToResolve((segments ++ Seq(next)).render) + + s" ${segments.render} resolves to a Task with no children." } Left(errorMsg) @@ -45,7 +43,6 @@ object ResolveNonEmpty{ } } - def unableToResolve(segments: String): String = "Cannot resolve " + segments + "." def hintList(revSelectorsSoFar: Seq[Segment]) = { @@ -66,11 +63,11 @@ object ResolveNonEmpty{ } def errorMsgLabel( - given: String, - possibleMembers: Set[String], - prefixSegments: Segments, - fullSegments: Segments - ) = { + given: String, + possibleMembers: Set[String], + prefixSegments: Segments, + fullSegments: Segments + ) = { val suggestion = findMostSimilar(given, possibleMembers) match { case None => hintListLabel(prefixSegments.value) case Some(similar) => @@ -85,11 +82,11 @@ object ResolveNonEmpty{ } def errorMsgCross( - givenKeys: Seq[String], - possibleCrossKeys: Set[Seq[String]], - prefixSegments: Segments, - fullSegments: Segments - ) = { + givenKeys: Seq[String], + possibleCrossKeys: Set[Seq[String]], + prefixSegments: Segments, + fullSegments: Segments + ) = { val suggestion = findMostSimilar( givenKeys.mkString(","), From 9e7a40b5d87d41cc584fcbb117c032da07b23675 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 11:04:25 +0800 Subject: [PATCH 19/57] cleanup --- main/src/mill/main/Resolve.scala | 57 ++------------------------------ 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index a6cc684e9b7..fc558613fc3 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -108,8 +108,8 @@ trait Resolve[T] { } } for { - taskss <- EitherOps.sequence(selected).map(_.toList) - res <- EitherOps.sequence(taskss) + taskLists <- EitherOps.sequence(selected).map(_.toList) + res <- EitherOps.sequence(taskLists) } yield res.flatten } EitherOps.sequence(resolved) @@ -135,57 +135,4 @@ trait Resolve[T] { } yield rootModule } } - - def resolveTasks[R]( - resolver: mill.main.Resolve[R], - evaluator: Evaluator, - scriptArgs: Seq[String], - selectMode: SelectMode - ): Either[String, List[R]] = { - val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) - val resolvedGroups = parsedGroups.flatMap { groups => - val resolved = groups.map { parsed: TargetsWithParams => - resolveTasks(resolver, evaluator, Right(parsed)) - } - EitherOps.sequence(resolved) - } - resolvedGroups.map(_.flatten.toList) - } - - private def resolveTasks[R]( - resolver: mill.main.Resolve[R], - evaluator: Evaluator, - targetsWithParams: Either[String, TargetsWithParams] - ): Either[String, List[R]] = { - for { - parsed <- targetsWithParams - (selectors, args) = parsed - taskss <- { - val selected = selectors.map { case (scopedSel, sel) => - for (rootModule <- resolveRootModule(evaluator, scopedSel)) - yield { - - try { - // We inject the `evaluator.rootModule` into the TargetScopt, rather - // than the `rootModule`, because even if you are running an external - // module we still want you to be able to resolve targets from your - // main build. Resolving targets from external builds as CLI arguments - // is not currently supported - mill.eval.Evaluator.currentEvaluator.set(evaluator) - resolver.resolveNonEmpty( - sel.value.toList, - rootModule, - rootModule.millDiscover, - args - ) - } finally { - mill.eval.Evaluator.currentEvaluator.set(null) - } - } - } - EitherOps.sequence(selected).map(_.toList) - } - res <- EitherOps.sequence(taskss) - } yield res.flatten - } } From 35cfef00339c443d5544cd28394b7ff606725c29 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 11:20:43 +0800 Subject: [PATCH 20/57] cleanup --- main/src/mill/main/Resolve.scala | 113 +++++++++---------- main/test/src/mill/main/ResolversTests.scala | 11 +- 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index fc558613fc3..fca1456fa07 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -1,6 +1,5 @@ package mill.main -import mill.define.ParseArgs.TargetsWithParams import mill.define.{ BaseModule, Discover, @@ -17,106 +16,96 @@ import mill.main.ResolveCore.Resolved import mill.util.EitherOps object ResolveSegments extends Resolve[Segments] { - def resolveNonEmpty( - selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String] - ) = { - ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => - value.map(_.segments) - } + def handleResolved(resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + selector: Segments) = { + Right(resolved.map(_.segments)) } } object ResolveMetadata extends Resolve[String] { - def resolveNonEmpty( - selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String] - ) = { - ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).map { value => - value.map(_.segments.render) - } + def handleResolved(resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + selector: Segments) = { + Right(resolved.map(_.segments.render)) } } object ResolveTasks extends Resolve[NamedTask[Any]] { - def resolveNonEmpty( - selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String] - ) = { - ResolveNonEmpty.resolveNonEmpty(selector, current, discover, args).flatMap { value => - val taskList: Set[Either[String, NamedTask[_]]] = value.collect { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - case Resolved.Module(value: TaskModule) => - ResolveCore.resolveDirectChildren( - value, - Some(value.defaultCommandName()), - discover, - args - ).values.head.flatMap { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - } - } + def handleResolved(resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + selector: Segments) = { - if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) - else Left(s"Cannot find default task to evaluate for module ${Segments(selector).render}") + val taskList: Set[Either[String, NamedTask[_]]] = resolved.collect { + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + case Resolved.Module(value: TaskModule) => + ResolveCore.resolveDirectChildren( + value, + Some(value.defaultCommandName()), + discover, + args + ).values.head.flatMap { + case Resolved.Target(value) => Right(value) + case Resolved.Command(value) => value() + } } + if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) + else Left(s"Cannot find default task to evaluate for module ${selector.render}") } } trait Resolve[T] { - def resolveNonEmpty( - selector: List[Segment], - current: BaseModule, - discover: Discover[_], - args: Seq[String] - ): Either[String, Set[T]] + def handleResolved(resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + segments: Segments): Either[String, Set[T]] def resolveTasks( evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, List[T]] = { - val parsedGroups: Either[String, Seq[TargetsWithParams]] = ParseArgs(scriptArgs, selectMode) - val resolvedGroups = parsedGroups.flatMap { groups => + val resolvedGroups = ParseArgs(scriptArgs, selectMode).flatMap { groups => val resolved = groups.map { case (selectors, args) => val selected = selectors.map { case (scopedSel, sel) => - for (rootModule <- resolveRootModule(evaluator, scopedSel)) - yield try { + resolveRootModule(evaluator, scopedSel).map{rootModule => + try { // We inject the `evaluator.rootModule` into the TargetScopt, rather // than the `rootModule`, because even if you are running an external // module we still want you to be able to resolve targets from your // main build. Resolving targets from external builds as CLI arguments // is not currently supported mill.eval.Evaluator.currentEvaluator.set(evaluator) - resolveNonEmpty( - sel.value.toList, - rootModule, - rootModule.millDiscover, - args - ) + + resolveNonEmptyAndHandle(args, sel, rootModule) } finally { mill.eval.Evaluator.currentEvaluator.set(null) } + } } - for { - taskLists <- EitherOps.sequence(selected).map(_.toList) - res <- EitherOps.sequence(taskLists) - } yield res.flatten + + EitherOps + .sequence(selected) + .flatMap(EitherOps.sequence(_)) + .map(_.flatten) } + EitherOps.sequence(resolved) } + resolvedGroups.map(_.flatten.toList) } + def resolveNonEmptyAndHandle(args: Seq[String], sel: Segments, rootModule: BaseModule): Either[String, Set[T]] = { + ResolveNonEmpty.resolveNonEmpty(sel.value.toList, rootModule, rootModule.millDiscover, args) + .flatMap(handleResolved(_, rootModule.millDiscover, args, sel)) + } + def resolveRootModule(evaluator: Evaluator, scopedSel: Option[Segments]) = { scopedSel match { case None => Right(evaluator.rootModule) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 7193603ea19..7d45bc03382 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -1,6 +1,6 @@ package mill.main -import mill.define.{NamedTask, Segment, SelectMode} +import mill.define.{NamedTask, Segment, Segments, SelectMode} import mill.util.TestGraphs._ import utest._ object ResolversTests extends TestSuite { @@ -18,11 +18,10 @@ object ResolversTests extends TestSuite { val expected = expected0.map(_.map(_(module))) val resolved = for { selectors <- mill.define.ParseArgs(selectorStrings, SelectMode.Single).map(_.head._1.head) - task <- mill.main.ResolveTasks.resolveNonEmpty( - selectors._2.value.toList, - module, - module.millDiscover, - Nil + task <- mill.main.ResolveTasks.resolveNonEmptyAndHandle( + Nil, + Segments(selectors._2.value.toList), + module ) } yield task // doesn't work for commands, don't know why From 4862477691dbf176e270f65ec26876243534f976 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 12:22:48 +0800 Subject: [PATCH 21/57] wip --- .../contrib/scoverage/ScoverageReport.scala | 6 +- main/core/src/mill/define/ExpandBraces.scala | 50 +++++++++++++ main/core/src/mill/define/ParseArgs.scala | 72 +------------------ main/src/mill/main/Resolve.scala | 64 +++++++++++------ main/test/src/mill/main/ResolversTests.scala | 41 ++++++++--- main/test/src/mill/util/ParseArgsTest.scala | 6 +- 6 files changed, 132 insertions(+), 107 deletions(-) create mode 100644 main/core/src/mill/define/ExpandBraces.scala diff --git a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala index 2c0f5e29bb0..ebee64f1615 100644 --- a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala +++ b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala @@ -3,7 +3,7 @@ package mill.contrib.scoverage import mill.contrib.scoverage.api.ScoverageReportWorkerApi.ReportType import mill.define.{Command, Module, Task} import mill.eval.Evaluator -import mill.main.RunScript +import mill.main.{ResolveTasks, RunScript} import mill.define.SelectMode import mill.{PathRef, T} import os.Path @@ -81,7 +81,7 @@ trait ScoverageReport extends Module { sources: String, dataTargets: String ): Task[PathRef] = { - val sourcesTasks: Seq[Task[Seq[PathRef]]] = RunScript.resolveTasks( + val sourcesTasks: Seq[Task[Seq[PathRef]]] = ResolveTasks.resolveTasks( evaluator, Seq(sources), SelectMode.Single @@ -89,7 +89,7 @@ trait ScoverageReport extends Module { case Left(err) => throw new Exception(err) case Right(tasks) => tasks.asInstanceOf[Seq[Task[Seq[PathRef]]]] } - val dataTasks: Seq[Task[PathRef]] = RunScript.resolveTasks( + val dataTasks: Seq[Task[PathRef]] = ResolveTasks.resolveTasks( evaluator, Seq(dataTargets), SelectMode.Single diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala new file mode 100644 index 00000000000..8b9efaa1165 --- /dev/null +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -0,0 +1,50 @@ +package mill.define +import fastparse._ +import fastparse.NoWhitespace.noWhitespaceImplicit + +object ExpandBraces{ + private sealed trait Fragment + private object Fragment { + case class Keep(value: String) extends Fragment + case class Expand(values: List[List[Fragment]]) extends Fragment + } + + def expandBraces(selectorString: String): Either[String, Seq[String]] = { + parse(selectorString, parser(_)) match { + case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}") + case Parsed.Success(fragmentLists, _) => + def processFragmentSequence(remaining: List[Fragment]): List[List[String]] = + remaining match { + case Nil => List(List()) + case head :: tail => + val tailStrings = processFragmentSequence(tail) + head match { + case Fragment.Keep(s) => tailStrings.map(s :: _) + case Fragment.Expand(fragmentLists) => + for { + lhs <- fragmentLists.flatMap(processFragmentSequence) + rhs <- tailStrings + } yield lhs ::: rhs + } + } + + val res = processFragmentSequence(fragmentLists.toList).map(_.mkString) + + Right(res) + } + } + + private def plainChars[_p: P]: P[Fragment.Keep] = + P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep) + + private def toExpand[_p: P]: P[Fragment] = + P("{" ~ braceParser.rep(1).rep(sep = ",") ~ "}").map(x => + Fragment.Expand(x.toList.map(_.toList)) + ) + + private def braceParser[_p: P]: P[Fragment] = P(toExpand | plainChars) + + private def topLevelComma[_p: P] = P(",".!).map(Fragment.Keep(_)) + + private def parser[_p: P]: P[Seq[Fragment]] = P((braceParser | topLevelComma).rep(1) ~ End) +} diff --git a/main/core/src/mill/define/ParseArgs.scala b/main/core/src/mill/define/ParseArgs.scala index 846a8cf923a..b14fc85da81 100644 --- a/main/core/src/mill/define/ParseArgs.scala +++ b/main/core/src/mill/define/ParseArgs.scala @@ -70,7 +70,7 @@ object ParseArgs { for { _ <- validateSelectors(selectors) expandedSelectors <- EitherOps - .sequence(selectors.map(expandBraces)) + .sequence(selectors.map(ExpandBraces.expandBraces)) .map(_.flatten) selectors <- EitherOps.sequence(expandedSelectors.map(extractSegments)) } yield (selectors.toList, args) @@ -98,76 +98,6 @@ object ParseArgs { else Right(()) } - def expandBraces(selectorString: String): Either[String, List[String]] = { - parseBraceExpansion(selectorString) match { - case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}") - case Parsed.Success(expanded, _) => Right(expanded.toList) - } - } - - private sealed trait Fragment - private object Fragment { - case class Keep(value: String) extends Fragment - case class Expand(values: List[List[Fragment]]) extends Fragment - - def unfold(fragments: List[Fragment]): Seq[String] = { - fragments match { - case head :: rest => - val prefixes = head match { - case Keep(v) => Seq(v) - case Expand(Nil) => Seq("{}") - case Expand(List(vs)) => unfold(vs).map("{" + _ + "}") - case Expand(vss) => vss.flatMap(unfold) - } - for { - prefix <- prefixes - suffix <- unfold(rest) - } yield prefix + suffix - - case Nil => Seq("") - } - } - } - - private object BraceExpansionParser { - def plainChars[_p: P]: P[Fragment.Keep] = - P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep) - - def toExpand[_p: P]: P[Fragment] = - P("{" ~ braceParser.rep(1).rep(sep = ",") ~ "}").map(x => - Fragment.Expand(x.toList.map(_.toList)) - ) - - def braceParser[_p: P]: P[Fragment] = P(toExpand | plainChars) - - def parser[_p: P]: P[Seq[String]] = P(braceParser.rep(1).rep(sep = ",") ~ End).map { vss => - def unfold(vss: List[Seq[String]]): Seq[String] = { - vss match { - case Nil => Seq("") - case head :: rest => - for { - str <- head - r <- unfold(rest) - } yield r match { - case "" => str - case _ => str + "," + r - } - } - } - - val stringss = vss.map(x => Fragment.unfold(x.toList)).toList - unfold(stringss) - } - } - - private def parseBraceExpansion(input: String): Parsed[Seq[String]] = { - - parse( - input, - BraceExpansionParser.parser(_) - ) - } - def extractSegments(selectorString: String): Either[String, (Option[Segments], Segments)] = parseSelector(selectorString) match { case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}") diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index fca1456fa07..acc204264ad 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -16,28 +16,34 @@ import mill.main.ResolveCore.Resolved import mill.util.EitherOps object ResolveSegments extends Resolve[Segments] { - def handleResolved(resolved: Set[Resolved], - discover: Discover[_], - args: Seq[String], - selector: Segments) = { + def handleResolved( + resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + selector: Segments + ) = { Right(resolved.map(_.segments)) } } object ResolveMetadata extends Resolve[String] { - def handleResolved(resolved: Set[Resolved], - discover: Discover[_], - args: Seq[String], - selector: Segments) = { + def handleResolved( + resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + selector: Segments + ) = { Right(resolved.map(_.segments.render)) } } object ResolveTasks extends Resolve[NamedTask[Any]] { - def handleResolved(resolved: Set[Resolved], - discover: Discover[_], - args: Seq[String], - selector: Segments) = { + def handleResolved( + resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + selector: Segments + ) = { val taskList: Set[Either[String, NamedTask[_]]] = resolved.collect { case Resolved.Target(value) => Right(value) @@ -60,27 +66,37 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { } trait Resolve[T] { - def handleResolved(resolved: Set[Resolved], - discover: Discover[_], - args: Seq[String], - segments: Segments): Either[String, Set[T]] + def handleResolved( + resolved: Set[Resolved], + discover: Discover[_], + args: Seq[String], + segments: Segments + ): Either[String, Set[T]] def resolveTasks( evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode + ): Either[String, List[T]] = { + resolveTasks0(Some(evaluator), evaluator.rootModule, scriptArgs, selectMode) + } + def resolveTasks0( + evaluatorOpt: Option[Evaluator], + baseModule: BaseModule, + scriptArgs: Seq[String], + selectMode: SelectMode ): Either[String, List[T]] = { val resolvedGroups = ParseArgs(scriptArgs, selectMode).flatMap { groups => val resolved = groups.map { case (selectors, args) => val selected = selectors.map { case (scopedSel, sel) => - resolveRootModule(evaluator, scopedSel).map{rootModule => + resolveRootModule(baseModule, scopedSel).map { rootModule => try { // We inject the `evaluator.rootModule` into the TargetScopt, rather // than the `rootModule`, because even if you are running an external // module we still want you to be able to resolve targets from your // main build. Resolving targets from external builds as CLI arguments // is not currently supported - mill.eval.Evaluator.currentEvaluator.set(evaluator) + evaluatorOpt.foreach(mill.eval.Evaluator.currentEvaluator.set(_)) resolveNonEmptyAndHandle(args, sel, rootModule) } finally { @@ -101,18 +117,22 @@ trait Resolve[T] { resolvedGroups.map(_.flatten.toList) } - def resolveNonEmptyAndHandle(args: Seq[String], sel: Segments, rootModule: BaseModule): Either[String, Set[T]] = { + def resolveNonEmptyAndHandle( + args: Seq[String], + sel: Segments, + rootModule: BaseModule + ): Either[String, Set[T]] = { ResolveNonEmpty.resolveNonEmpty(sel.value.toList, rootModule, rootModule.millDiscover, args) .flatMap(handleResolved(_, rootModule.millDiscover, args, sel)) } - def resolveRootModule(evaluator: Evaluator, scopedSel: Option[Segments]) = { + def resolveRootModule(rootModule: BaseModule, scopedSel: Option[Segments]) = { scopedSel match { - case None => Right(evaluator.rootModule) + case None => Right(rootModule) case Some(scoping) => for { moduleCls <- - try Right(evaluator.rootModule.getClass.getClassLoader.loadClass(scoping.render + "$")) + try Right(rootModule.getClass.getClassLoader.loadClass(scoping.render + "$")) catch { case e: ClassNotFoundException => Left("Cannot resolve external module " + scoping.render) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 7d45bc03382..ccb38c97417 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -17,16 +17,15 @@ object ResolversTests extends TestSuite { val expected = expected0.map(_.map(_(module))) val resolved = for { - selectors <- mill.define.ParseArgs(selectorStrings, SelectMode.Single).map(_.head._1.head) - task <- mill.main.ResolveTasks.resolveNonEmptyAndHandle( - Nil, - Segments(selectors._2.value.toList), - module + task <- mill.main.ResolveTasks.resolveTasks0( + None, + module, + selectorStrings, + SelectMode.Separated ) } yield task - // doesn't work for commands, don't know why - // assert(resolved == expected) - assert(resolved.map(_.map(_.toString)) == expected.map(_.map(_.toString))) + + assert(resolved.map(_.map(_.toString).toSet[String]) == expected.map(_.map(_.toString))) } val tests = Tests { @@ -35,6 +34,7 @@ object ResolversTests extends TestSuite { "single" - { val check = ResolversTests.check(singleton) _ "pos" - check("single", Right(Set(_.single))) + "posCurly" - check("{single}", Right(Set(_.single))) "neg1" - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) "neg2" - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) "neg3" - check("nsiigle", Left("Cannot resolve nsiigle. Did you mean single?")) @@ -82,6 +82,27 @@ object ResolversTests extends TestSuite { "pos1" - check("single", Right(Set(_.single))) "pos2" - check("nested.single", Right(Set(_.nested.single))) "pos3" - check("classInstance.single", Right(Set(_.classInstance.single))) + "posCurly1" - check("classInstance.{single}", Right(Set(_.classInstance.single))) + "posCurly2" - check( + "{nested,classInstance}.single", + Right(Set(_.nested.single, _.classInstance.single)) + ) + "posCurly3" - check( + "{nested,classInstance}.{single}", + Right(Set(_.nested.single, _.classInstance.single)) + ) + "posCurly4" - check( + "{single,{nested,classInstance}.{single}}", + Right(Set(_.single, _.nested.single, _.classInstance.single)) + ) + "posCurly5" - check( + "{single,nested.single,classInstance.single}", + Right(Set(_.single, _.nested.single, _.classInstance.single)) + ) + "posCurly6" - check( + "{{single},{nested,classInstance}.{single}}", + Right(Set(_.single, _.nested.single, _.classInstance.single)) + ) "neg1" - check( "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") @@ -176,6 +197,10 @@ object ResolversTests extends TestSuite { val check = ResolversTests.check(singleCross) _ "pos1" - check("cross[210].suffix", Right(Set(_.cross("210").suffix))) "pos2" - check("cross[211].suffix", Right(Set(_.cross("211").suffix))) + "posCurly" - check( + "cross[{210,211}].suffix", + Right(Set(_.cross("210").suffix, _.cross("211").suffix)) + ) "neg1" - check( "cross[210].doesntExist", Left( diff --git a/main/test/src/mill/util/ParseArgsTest.scala b/main/test/src/mill/util/ParseArgsTest.scala index 4acad350dae..e029ecfdd0d 100644 --- a/main/test/src/mill/util/ParseArgsTest.scala +++ b/main/test/src/mill/util/ParseArgsTest.scala @@ -1,6 +1,6 @@ package mill.util -import mill.define.{ParseArgs, Segment, Segments, SelectMode} +import mill.define.{ExpandBraces, ParseArgs, Segment, Segments, SelectMode} import mill.define.Segment.{Cross, Label} import mill.define.ParseArgs.{TargetSeparator, TargetsWithParams} import utest._ @@ -88,7 +88,7 @@ object ParseArgsTest extends TestSuite { } "expandBraces" - { def check(input: String, expectedExpansion: List[String]) = { - val Right(expanded) = ParseArgs.expandBraces(input) + val Right(expanded) = ExpandBraces.expandBraces(input) assert(expanded == expectedExpansion) } @@ -128,7 +128,7 @@ object ParseArgsTest extends TestSuite { val malformed = Seq("core.{compile", "core.{compile,test]") malformed.foreach { m => - val Left(error) = ParseArgs.expandBraces(m) + val Left(error) = ExpandBraces.expandBraces(m) assert(error.contains("Parsing exception")) } } From b274839654d8d22cca78e7f9128dc9f7da1a5d44 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 12:26:00 +0800 Subject: [PATCH 22/57] split out ExpandBracesTests --- .../src/mill/define/ExpandBracesTests.scala | 69 +++++++++++++++++++ .../ParseArgsTests.scala} | 67 +----------------- main/test/src/mill/main/ResolversTests.scala | 2 +- 3 files changed, 73 insertions(+), 65 deletions(-) create mode 100644 main/test/src/mill/define/ExpandBracesTests.scala rename main/test/src/mill/{util/ParseArgsTest.scala => define/ParseArgsTests.scala} (83%) diff --git a/main/test/src/mill/define/ExpandBracesTests.scala b/main/test/src/mill/define/ExpandBracesTests.scala new file mode 100644 index 00000000000..b7adf9219f9 --- /dev/null +++ b/main/test/src/mill/define/ExpandBracesTests.scala @@ -0,0 +1,69 @@ +package mill.define + +import utest._ + +object ExpandBracesTests extends TestSuite { + + val tests = Tests { + "expandBraces" - { + def check(input: String, expectedExpansion: List[String]) = { + val Right(expanded) = ExpandBraces.expandBraces(input) + + assert(expanded == expectedExpansion) + } + + "expandLeft" - check( + "{application,core}.compile", + List("application.compile", "core.compile") + ) + "expandRight" - check( + "application.{jar,docJar,sourcesJar}", + List("application.jar", "application.docJar", "application.sourcesJar") + ) + "expandBoth" - check( + "{core,application}.{jar,docJar}", + List( + "core.jar", + "core.docJar", + "application.jar", + "application.docJar" + ) + ) + "expandNested" - { + check("{hello,world.{cow,moo}}", List("hello", "world.cow", "world.moo")) + check("{a,b{c,d}}", List("a", "bc", "bd")) + check("{a,b,{c,d}}", List("a", "b", "c", "d")) + check("{a,b{c,d{e,f}}}", List("a", "bc", "bde", "bdf")) + check("{a{b,c},d}", List("ab", "ac", "d")) + check("{a,{b,c}d}", List("a", "bd", "cd")) + check("{a{b,c},d{e,f}}", List("ab", "ac", "de", "df")) + check("{a,b{c,d},e{f,g}}", List("a", "bc", "bd", "ef", "eg")) + } + "expandMixed" - check( + "{a,b}.{c}.{}.e", + List("a.{c}.{}.e", "b.{c}.{}.e") + ) + "malformed" - { + val malformed = Seq("core.{compile", "core.{compile,test]") + + malformed.foreach { m => + val Left(error) = ExpandBraces.expandBraces(m) + assert(error.contains("Parsing exception")) + } + } + "dontExpand" - { + check("core.compile", List("core.compile")) + check("{}.compile", List("{}.compile")) + check("{core}.compile", List("{core}.compile")) + } + "keepUnknownSymbols" - { + check("{a,b}.e<>", List("a.e<>", "b.e<>")) + check("a[99]&&", List("a[99]&&")) + check( + "{a,b}.<%%>.{c,d}", + List("a.<%%>.c", "a.<%%>.d", "b.<%%>.c", "b.<%%>.d") + ) + } + } + } +} diff --git a/main/test/src/mill/util/ParseArgsTest.scala b/main/test/src/mill/define/ParseArgsTests.scala similarity index 83% rename from main/test/src/mill/util/ParseArgsTest.scala rename to main/test/src/mill/define/ParseArgsTests.scala index e029ecfdd0d..7dba6457ceb 100644 --- a/main/test/src/mill/util/ParseArgsTest.scala +++ b/main/test/src/mill/define/ParseArgsTests.scala @@ -1,11 +1,10 @@ -package mill.util +package mill.define -import mill.define.{ExpandBraces, ParseArgs, Segment, Segments, SelectMode} +import mill.define.ParseArgs.TargetSeparator import mill.define.Segment.{Cross, Label} -import mill.define.ParseArgs.{TargetSeparator, TargetsWithParams} import utest._ -object ParseArgsTest extends TestSuite { +object ParseArgsTests extends TestSuite { val tests = Tests { "extractSelsAndArgs" - { @@ -86,66 +85,6 @@ object ParseArgsTest extends TestSuite { multiSelect = true ) } - "expandBraces" - { - def check(input: String, expectedExpansion: List[String]) = { - val Right(expanded) = ExpandBraces.expandBraces(input) - - assert(expanded == expectedExpansion) - } - - "expandLeft" - check( - "{application,core}.compile", - List("application.compile", "core.compile") - ) - "expandRight" - check( - "application.{jar,docJar,sourcesJar}", - List("application.jar", "application.docJar", "application.sourcesJar") - ) - "expandBoth" - check( - "{core,application}.{jar,docJar}", - List( - "core.jar", - "core.docJar", - "application.jar", - "application.docJar" - ) - ) - "expandNested" - { - check("{hello,world.{cow,moo}}", List("hello", "world.cow", "world.moo")) - check("{a,b{c,d}}", List("a", "bc", "bd")) - check("{a,b,{c,d}}", List("a", "b", "c", "d")) - check("{a,b{c,d{e,f}}}", List("a", "bc", "bde", "bdf")) - check("{a{b,c},d}", List("ab", "ac", "d")) - check("{a,{b,c}d}", List("a", "bd", "cd")) - check("{a{b,c},d{e,f}}", List("ab", "ac", "de", "df")) - check("{a,b{c,d},e{f,g}}", List("a", "bc", "bd", "ef", "eg")) - } - "expandMixed" - check( - "{a,b}.{c}.{}.e", - List("a.{c}.{}.e", "b.{c}.{}.e") - ) - "malformed" - { - val malformed = Seq("core.{compile", "core.{compile,test]") - - malformed.foreach { m => - val Left(error) = ExpandBraces.expandBraces(m) - assert(error.contains("Parsing exception")) - } - } - "dontExpand" - { - check("core.compile", List("core.compile")) - check("{}.compile", List("{}.compile")) - check("{core}.compile", List("{core}.compile")) - } - "keepUnknownSymbols" - { - check("{a,b}.e<>", List("a.e<>", "b.e<>")) - check("a[99]&&", List("a[99]&&")) - check( - "{a,b}.<%%>.{c,d}", - List("a.<%%>.c", "a.<%%>.d", "b.<%%>.c", "b.<%%>.d") - ) - } - } "apply(multiselect)" - { def check( diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index ccb38c97417..17c7054dff7 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -1,6 +1,6 @@ package mill.main -import mill.define.{NamedTask, Segment, Segments, SelectMode} +import mill.define.{NamedTask, SelectMode} import mill.util.TestGraphs._ import utest._ object ResolversTests extends TestSuite { From 3e186c8da316a7f4ea56418d3f83124e91436766 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 12:28:14 +0800 Subject: [PATCH 23/57] cleanup --- main/core/src/mill/define/ParseArgs.scala | 16 ++++++++-------- main/test/src/mill/main/ResolversTests.scala | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/main/core/src/mill/define/ParseArgs.scala b/main/core/src/mill/define/ParseArgs.scala index b14fc85da81..35d48baa363 100644 --- a/main/core/src/mill/define/ParseArgs.scala +++ b/main/core/src/mill/define/ParseArgs.scala @@ -99,7 +99,7 @@ object ParseArgs { } def extractSegments(selectorString: String): Either[String, (Option[Segments], Segments)] = - parseSelector(selectorString) match { + parse(selectorString, selector(_)) match { case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}") case Parsed.Success(selector, _) => Right(selector) } @@ -110,17 +110,17 @@ object ParseArgs { def isLegalIdentifier(identifier: String): Boolean = parse(identifier, standaloneIdent(_)).isInstanceOf[Parsed.Success[_]] - private def parseSelector(input: String): Parsed[(Option[Segments], Segments)] = { - def ident2[_p: P] = P(CharsWhileIn("a-zA-Z0-9_\\-.")).! - def segment[_p: P] = P(ident).map(Segment.Label) - def crossSegment[_p: P] = P("[" ~ ident2.rep(1, sep = ",") ~ "]").map(Segment.Cross) - def simpleQuery[_p: P] = P(segment ~ ("." ~ segment | crossSegment).rep).map { + private def selector[_p: P]: P[(Option[Segments], Segments)] = { + def ident2 = P(CharsWhileIn("a-zA-Z0-9_\\-.")).! + def segment = P(ident).map(Segment.Label) + def crossSegment = P("[" ~ ident2.rep(1, sep = ",") ~ "]").map(Segment.Cross) + def simpleQuery = P(segment ~ ("." ~ segment | crossSegment).rep).map { case (h, rest) => Segments(h +: rest) } - def query[_p: P] = P(simpleQuery ~ ("/" ~/ simpleQuery).?).map { + + P(simpleQuery ~ ("/" ~/ simpleQuery).? ~ End).map { case (q, None) => (None, q) case (q, Some(q2)) => (Some(q), q2) } - parse(input, query(_)) } } diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 17c7054dff7..aa223d3fda4 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -64,9 +64,13 @@ object ResolversTests extends TestSuite { ) "neg4" - check("", Left("Selector cannot be empty")) "neg5" - check( - "invisible&", + "invisible", Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.") ) + "negBadParse" - check( + "invisible&", + Left("Parsing exception Position 1:10, found \"&\"") + ) "nested" - { "pos" - check("nested-module.nested-target", Right(Set(_.`nested-module`.`nested-target`))) "neg" - check( From 424a1a9cdd0f8f03117492603ede2cee37ae349e Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 12:37:19 +0800 Subject: [PATCH 24/57] fix --- main/core/src/mill/define/ExpandBraces.scala | 3 ++- main/test/src/mill/define/ExpandBracesTests.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala index 8b9efaa1165..aa90d7358df 100644 --- a/main/core/src/mill/define/ExpandBraces.scala +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -21,7 +21,8 @@ object ExpandBraces{ head match { case Fragment.Keep(s) => tailStrings.map(s :: _) case Fragment.Expand(fragmentLists) => - for { + if (fragmentLists == Nil) tailStrings.map("{}" :: _) + else for { lhs <- fragmentLists.flatMap(processFragmentSequence) rhs <- tailStrings } yield lhs ::: rhs diff --git a/main/test/src/mill/define/ExpandBracesTests.scala b/main/test/src/mill/define/ExpandBracesTests.scala index b7adf9219f9..903b123d896 100644 --- a/main/test/src/mill/define/ExpandBracesTests.scala +++ b/main/test/src/mill/define/ExpandBracesTests.scala @@ -41,7 +41,7 @@ object ExpandBracesTests extends TestSuite { } "expandMixed" - check( "{a,b}.{c}.{}.e", - List("a.{c}.{}.e", "b.{c}.{}.e") + List("a.c.{}.e", "b.c.{}.e") ) "malformed" - { val malformed = Seq("core.{compile", "core.{compile,test]") From 46693402d84607cca5ede4bd284ca727812741c1 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 12:50:12 +0800 Subject: [PATCH 25/57] fixes --- main/core/src/mill/define/ExpandBraces.scala | 17 ++++++++++++----- .../src/mill/define/ExpandBracesTests.scala | 18 +++++++++++------- main/test/src/mill/main/ResolversTests.scala | 18 ++++++------------ 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala index aa90d7358df..119be6dc143 100644 --- a/main/core/src/mill/define/ExpandBraces.scala +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -21,8 +21,12 @@ object ExpandBraces{ head match { case Fragment.Keep(s) => tailStrings.map(s :: _) case Fragment.Expand(fragmentLists) => - if (fragmentLists == Nil) tailStrings.map("{}" :: _) - else for { + if (fragmentLists.length == 1) { + for { + lhs <- fragmentLists.flatMap(processFragmentSequence) + rhs <- tailStrings + } yield List("{") ::: lhs ::: List("}") ::: rhs + } else for { lhs <- fragmentLists.flatMap(processFragmentSequence) rhs <- tailStrings } yield lhs ::: rhs @@ -36,12 +40,15 @@ object ExpandBraces{ } private def plainChars[_p: P]: P[Fragment.Keep] = - P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep) + P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep).log + + + private def emptyExpansionBranch[_p: P] = P("").map(_ => List(Fragment.Keep(""))) private def toExpand[_p: P]: P[Fragment] = - P("{" ~ braceParser.rep(1).rep(sep = ",") ~ "}").map(x => + P("{" ~ (braceParser.rep(1) | emptyExpansionBranch).rep(sep = ",") ~ "}").map(x => Fragment.Expand(x.toList.map(_.toList)) - ) + ).log private def braceParser[_p: P]: P[Fragment] = P(toExpand | plainChars) diff --git a/main/test/src/mill/define/ExpandBracesTests.scala b/main/test/src/mill/define/ExpandBracesTests.scala index 903b123d896..037aee122e1 100644 --- a/main/test/src/mill/define/ExpandBracesTests.scala +++ b/main/test/src/mill/define/ExpandBracesTests.scala @@ -39,10 +39,13 @@ object ExpandBracesTests extends TestSuite { check("{a{b,c},d{e,f}}", List("ab", "ac", "de", "df")) check("{a,b{c,d},e{f,g}}", List("a", "bc", "bd", "ef", "eg")) } - "expandMixed" - check( - "{a,b}.{c}.{}.e", - List("a.c.{}.e", "b.c.{}.e") - ) + "expandMixed" - { + test - check( + "{a,b}.{c}.{}.e", + List("a.{c}.{}.e", "b.{c}.{}.e") + ) + test - check("{{b,c}}d", List("{b}d", "{c}d")) + } "malformed" - { val malformed = Seq("core.{compile", "core.{compile,test]") @@ -52,9 +55,10 @@ object ExpandBracesTests extends TestSuite { } } "dontExpand" - { - check("core.compile", List("core.compile")) - check("{}.compile", List("{}.compile")) - check("{core}.compile", List("{core}.compile")) + test - check("core.compile", List("core.compile")) + test - check("{}.compile", List("{}.compile")) + test - check("{core}.compile", List("{core}.compile")) + } "keepUnknownSymbols" - { check("{a,b}.e<>", List("a.e<>", "b.e<>")) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index aa223d3fda4..2344803e304 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -34,7 +34,6 @@ object ResolversTests extends TestSuite { "single" - { val check = ResolversTests.check(singleton) _ "pos" - check("single", Right(Set(_.single))) - "posCurly" - check("{single}", Right(Set(_.single))) "neg1" - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) "neg2" - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) "neg3" - check("nsiigle", Left("Cannot resolve nsiigle. Did you mean single?")) @@ -86,25 +85,20 @@ object ResolversTests extends TestSuite { "pos1" - check("single", Right(Set(_.single))) "pos2" - check("nested.single", Right(Set(_.nested.single))) "pos3" - check("classInstance.single", Right(Set(_.classInstance.single))) - "posCurly1" - check("classInstance.{single}", Right(Set(_.classInstance.single))) - "posCurly2" - check( + "posCurly1" - check( "{nested,classInstance}.single", Right(Set(_.nested.single, _.classInstance.single)) ) - "posCurly3" - check( - "{nested,classInstance}.{single}", - Right(Set(_.nested.single, _.classInstance.single)) - ) - "posCurly4" - check( - "{single,{nested,classInstance}.{single}}", + "posCurly2" - check( + "{single,{nested,classInstance}.single}", Right(Set(_.single, _.nested.single, _.classInstance.single)) ) - "posCurly5" - check( + "posCurly3" - check( "{single,nested.single,classInstance.single}", Right(Set(_.single, _.nested.single, _.classInstance.single)) ) - "posCurly6" - check( - "{{single},{nested,classInstance}.{single}}", + "posCurly4" - check( + "{,nested.,classInstance.}single", Right(Set(_.single, _.nested.single, _.classInstance.single)) ) "neg1" - check( From a229e4d8d86b377787b842752e273fba9dd9049c Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 12:50:53 +0800 Subject: [PATCH 26/57] resolveTasks -> resolve --- .../src/mill/contrib/scoverage/ScoverageReport.scala | 4 ++-- main/src/mill/main/MainModule.scala | 12 ++++++------ main/src/mill/main/Resolve.scala | 6 +++--- main/src/mill/main/RunScript.scala | 4 ++-- main/src/mill/main/TokenReaders.scala | 2 +- main/test/src/mill/main/ResolversTests.scala | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala index ebee64f1615..6a016a82a0a 100644 --- a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala +++ b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageReport.scala @@ -81,7 +81,7 @@ trait ScoverageReport extends Module { sources: String, dataTargets: String ): Task[PathRef] = { - val sourcesTasks: Seq[Task[Seq[PathRef]]] = ResolveTasks.resolveTasks( + val sourcesTasks: Seq[Task[Seq[PathRef]]] = ResolveTasks.resolve( evaluator, Seq(sources), SelectMode.Single @@ -89,7 +89,7 @@ trait ScoverageReport extends Module { case Left(err) => throw new Exception(err) case Right(tasks) => tasks.asInstanceOf[Seq[Task[Seq[PathRef]]]] } - val dataTasks: Seq[Task[PathRef]] = ResolveTasks.resolveTasks( + val dataTasks: Seq[Task[PathRef]] = ResolveTasks.resolve( evaluator, Seq(dataTargets), SelectMode.Single diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 1052f18ea27..5af7477a155 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -19,7 +19,7 @@ object MainModule { targets: Seq[String], selectMode: SelectMode )(f: List[NamedTask[Any]] => T): Result[T] = { - ResolveTasks.resolveTasks(evaluator, targets, selectMode) match { + ResolveTasks.resolve(evaluator, targets, selectMode) match { case Left(err) => Result.Failure(err) case Right(tasks) => Result.Success(f(tasks)) } @@ -76,7 +76,7 @@ trait MainModule extends mill.Module { * Resolves a mill query string and prints out the tasks it resolves to. */ def resolve(evaluator: Evaluator, targets: String*): Command[List[String]] = T.command { - val resolved: Either[String, List[String]] = ResolveMetadata.resolveTasks( + val resolved: Either[String, List[String]] = ResolveMetadata.resolve( evaluator, targets, SelectMode.Multi @@ -106,7 +106,7 @@ trait MainModule extends mill.Module { } private def plan0(evaluator: Evaluator, targets: Seq[String]) = { - ResolveTasks.resolveTasks( + ResolveTasks.resolve( evaluator, targets, SelectMode.Multi @@ -125,7 +125,7 @@ trait MainModule extends mill.Module { * chosen is arbitrary. */ def path(evaluator: Evaluator, src: String, dest: String): Command[List[String]] = T.command { - val resolved = ResolveTasks.resolveTasks( + val resolved = ResolveTasks.resolve( evaluator, List(src, dest), SelectMode.Multi @@ -319,7 +319,7 @@ trait MainModule extends mill.Module { if (targets.isEmpty) Right(os.list(rootDir).filterNot(keepPath)) else - mill.main.ResolveSegments.resolveTasks( + mill.main.ResolveSegments.resolve( evaluator, targets, SelectMode.Multi @@ -416,7 +416,7 @@ trait MainModule extends mill.Module { out.take() } - ResolveTasks.resolveTasks( + ResolveTasks.resolve( evaluator, targets, SelectMode.Multi diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index acc204264ad..84928028286 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -73,14 +73,14 @@ trait Resolve[T] { segments: Segments ): Either[String, Set[T]] - def resolveTasks( + def resolve( evaluator: Evaluator, scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, List[T]] = { - resolveTasks0(Some(evaluator), evaluator.rootModule, scriptArgs, selectMode) + resolve0(Some(evaluator), evaluator.rootModule, scriptArgs, selectMode) } - def resolveTasks0( + def resolve0( evaluatorOpt: Option[Evaluator], baseModule: BaseModule, scriptArgs: Seq[String], diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala index da513b8f035..c6535fb74c9 100644 --- a/main/src/mill/main/RunScript.scala +++ b/main/src/mill/main/RunScript.scala @@ -19,7 +19,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[ujson.Value])]])] = { - for (targets <- ResolveTasks.resolveTasks(evaluator, scriptArgs, selectMode)) + for (targets <- ResolveTasks.resolve(evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluate(evaluator, Agg.from(targets.distinct)) @@ -38,7 +38,7 @@ object RunScript { scriptArgs: Seq[String], selectMode: SelectMode ): Either[String, (Seq[PathRef], Either[String, Seq[(Any, Option[(TaskName, ujson.Value)])]])] = { - for (targets <- ResolveTasks.resolveTasks(evaluator, scriptArgs, selectMode)) + for (targets <- ResolveTasks.resolve(evaluator, scriptArgs, selectMode)) yield { val (watched, res) = evaluateNamed(evaluator, Agg.from(targets.distinct)) diff --git a/main/src/mill/main/TokenReaders.scala b/main/src/mill/main/TokenReaders.scala index 78a127869be..c1f57f1c431 100644 --- a/main/src/mill/main/TokenReaders.scala +++ b/main/src/mill/main/TokenReaders.scala @@ -11,7 +11,7 @@ object Tasks { extends mainargs.TokensReader[Tasks[T]]( shortName = "", read = s => - ResolveTasks.resolveTasks( + ResolveTasks.resolve( Evaluator.currentEvaluator.get, s, SelectMode.Single diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 2344803e304..ea487e7dc55 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -17,7 +17,7 @@ object ResolversTests extends TestSuite { val expected = expected0.map(_.map(_(module))) val resolved = for { - task <- mill.main.ResolveTasks.resolveTasks0( + task <- mill.main.ResolveTasks.resolve0( None, module, selectorStrings, From 7b74968408f90dc24b9f63470fa61b8bc977c2c5 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 13:40:22 +0800 Subject: [PATCH 27/57] things compile again after trying to make reflection return an Either --- main/core/src/mill/define/ExpandBraces.scala | 4 +- main/core/src/mill/define/Module.scala | 23 +++- main/src/mill/main/Resolve.scala | 27 +++-- main/src/mill/main/ResolveCore.scala | 112 ++++++++++++------- main/src/mill/main/ResolveNonEmpty.scala | 2 +- main/test/src/mill/main/ResolversTests.scala | 13 +++ main/test/src/mill/util/TestGraphs.scala | 23 ++++ 7 files changed, 147 insertions(+), 57 deletions(-) diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala index 119be6dc143..e6de9e64de6 100644 --- a/main/core/src/mill/define/ExpandBraces.scala +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -40,7 +40,7 @@ object ExpandBraces{ } private def plainChars[_p: P]: P[Fragment.Keep] = - P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep).log + P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep) private def emptyExpansionBranch[_p: P] = P("").map(_ => List(Fragment.Keep(""))) @@ -48,7 +48,7 @@ object ExpandBraces{ private def toExpand[_p: P]: P[Fragment] = P("{" ~ (braceParser.rep(1) | emptyExpansionBranch).rep(sep = ",") ~ "}").map(x => Fragment.Expand(x.toList.map(_.toList)) - ).log + ) private def braceParser[_p: P]: P[Fragment] = P(toExpand | plainChars) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 67b42056634..808265ab636 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -113,9 +113,20 @@ object Module { // For some reason, this fails to pick up concrete `object`s nested directly within // another top-level concrete `object`. This is fine for now, since Mill's Ammonite // script/REPL runner always wraps user code in a wrapper object/trait - def reflectNestedObjects[T: ClassTag](filter: String => Boolean = Function.const(true)) - : Seq[T] = { - reflect[T](filter) ++ + def reflectNestedObjects[T: ClassTag](filter: String => Boolean = Function.const(true)) = { + reflectNestedObjects0(filter).map(_._2()) + } + + def reflectNestedObjects0[T: ClassTag](filter: String => Boolean = Function.const(true)) + : Seq[(String, () => T)] = { + + val first = Module.reflect( + outer.getClass, + implicitly[ClassTag[T]].runtimeClass, + filter, + noParams = true + ).map(m => (m.getName, () => m.invoke(outer).asInstanceOf[T])) + val second = outer .getClass .getClasses @@ -123,12 +134,16 @@ object Module { .flatMap { c => c.getSimpleName match { case s"$name$$" if filter(name) => - c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T]) + c.getFields.find(_.getName == "MODULE$").map(f => (name, () => f.get(c).asInstanceOf[T])) case _ => None } } .distinct + + + + first ++ second } } } diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 84928028286..8f4b12c6bc1 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -46,18 +46,23 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { ) = { val taskList: Set[Either[String, NamedTask[_]]] = resolved.collect { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() - case Resolved.Module(value: TaskModule) => - ResolveCore.resolveDirectChildren( - value, - Some(value.defaultCommandName()), - discover, - args - ).values.head.flatMap { - case Resolved.Target(value) => Right(value) - case Resolved.Command(value) => value() + case r: Resolved.Target => r.valueOrErr + case r: Resolved.Command => r.valueOrErr + case r: Resolved.Module => + r.valueOrErr match { + case Right(value: TaskModule) => + ResolveCore.resolveDirectChildren( + value, + Some(value.defaultCommandName()), + discover, + args, + value.millModuleSegments + ).head match { + case r: Resolved.Target => r.valueOrErr + case r: Resolved.Command => r.valueOrErr + } } + } if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 2e7c08c93a9..dd9d01e6b37 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -23,16 +23,17 @@ object ResolveCore { } object Resolved { - case class Module(value: mill.define.Module) extends Resolved { - def segments = value.millModuleSegments - } - case class Target(value: mill.define.Target[_]) extends Resolved { - def segments = value.ctx.segments - } - case class Command(value: () => Either[String, mill.define.Command[_]]) - extends Resolved { - def segments = value().right.get.ctx.segments - } + case class Module(segments: Segments, + valueOrErr: Either[String, mill.define.Module]) + extends Resolved + + case class Target(segments: Segments, + valueOrErr: Either[String, mill.define.Target[_]]) + extends Resolved + + case class Command(segments: Segments, + valueOrErr: Either[String, mill.define.Command[_]]) + extends Resolved } sealed trait Result @@ -58,6 +59,7 @@ object ResolveCore { case Nil => Success(Set(current)) case head :: tail => val revSelectorsSoFar = head :: revSelectorsSoFar0 + val segments = Segments(revSelectorsSoFar.reverse) def recurse(searchModules: Set[Resolved]): Result = { val (failures, successesLists) = searchModules .map(resolve(tail, _, discover, args, revSelectorsSoFar)) @@ -77,26 +79,33 @@ object ResolveCore { } (head, current) match { - case (Segment.Label(singleLabel), Resolved.Module(obj)) => - EitherOps.sequence( + case (Segment.Label(singleLabel), m: Resolved.Module) => + + val resOrErr = m.valueOrErr.flatMap { obj => singleLabel match { case "__" => - obj - .millInternal - .modules - .flatMap(m => - Seq(Right(Resolved.Module(m))) ++ - resolveDirectChildren(m, None, discover, args).values + catchReflectException( + obj + .millInternal + .modules + ).map( + _.flatMap(m => + Seq(Resolved.Module(m.millModuleSegments, Right(m))) ++ + resolveDirectChildren(m, None, discover, args, m.millModuleSegments) ) - case "_" => resolveDirectChildren(obj, None, discover, args).values - case _ => resolveDirectChildren(obj, Some(singleLabel), discover, args).values + ) + case "_" => Right(resolveDirectChildren(obj, None, discover, args, segments)) + case _ => Right(resolveDirectChildren(obj, Some(singleLabel), discover, args, segments)) } - ) match { + } + + resOrErr match{ case Left(err) => Error(err) - case Right(v) => recurse(v.toSet) + case Right(res) => recurse(res.toSet) } - case (Segment.Cross(cross), Resolved.Module(c: Cross[_])) => + + case (Segment.Cross(cross), Resolved.Module(_, Right(c: Cross[_]))) => val searchModules: Seq[Module] = if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v else if (cross.contains("_")) { @@ -107,54 +116,76 @@ object ResolveCore { } yield v } else c.segmentsToModules.get(cross.toList).toSeq - recurse(searchModules.map(m => Resolved.Module(m)).toSet) + recurse(searchModules.map(m => Resolved.Module(m.millModuleSegments, Right(m))).toSet) case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) } } + def catchReflectException[T](t: => T): Either[String, T] = { + try Right(t) + catch { + case e: java.lang.reflect.InvocationTargetException => + val outerStack = new mill.api.Result.OuterStack(new Exception().getStackTrace) + Left(mill.api.Result.Exception(e.getCause, outerStack).toString) + } + } + def resolveDirectChildren( obj: Module, nameOpt: Option[String] = None, discover: Discover[_], - args: Seq[String] - ): Map[Segment, Either[String, Resolved]] = { + args: Seq[String], + segments: Segments + ): Set[Resolved] = { def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n) val modules = obj .millInternal - .reflectNestedObjects[Module](namePred) - .map(t => Segment.Label(t.millModuleSegments.parts.last) -> Right(Resolved.Module(t))) + .reflectNestedObjects0[Module](namePred) + .map{case (name, f) => + Resolved.Module( + segments ++ Seq(Segment.Label(name)), + catchReflectException(f()) + ) + } val crosses = obj match { case c: Cross[_] if nameOpt.isEmpty => - c.segmentsToModules.map { case (k, v) => Segment.Cross(k) -> Right(Resolved.Module(v)) } + c.segmentsToModules.map { case (k, v) => + Resolved.Module( + segments ++ Seq(Segment.Cross(k)), + catchReflectException(v) + ) + } case _ => Nil } val targets = Module .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) .map(m => - Segment.Label(decode(m.getName)) -> Right( - Resolved.Target(m.invoke(obj).asInstanceOf[Target[_]]) - ) + Resolved.Target( + segments ++ Seq(Segment.Label(decode(m.getName))), + catchReflectException(m.invoke(obj).asInstanceOf[Target[_]])) + ) val commands = Module .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) .map(m => decode(m.getName)) .map(name => - Segment.Label(name) -> Right(Resolved.Command(() => + Resolved.Command( + segments ++ Seq(Segment.Label(name)), invokeCommand( obj, name, discover.asInstanceOf[Discover[Module]], args ).head - )) + ) ) - (modules ++ crosses ++ targets ++ commands).toMap + (modules ++ crosses ++ targets ++ commands).toSet } def notFoundResult( @@ -163,18 +194,21 @@ object ResolveCore { next: Segment, discover: Discover[_], args: Seq[String] - ) = + ) = { + val segments = Segments(revSelectorsSoFar0.reverse) NotFound( - Segments(revSelectorsSoFar0.reverse), + segments, Set(current), next, current match { - case Resolved.Module(obj) => - resolveDirectChildren(obj, None, discover, args).keySet + case m: Resolved.Module => + resolveDirectChildren(m.valueOrErr.right.get, None, discover, args, segments) + .map(_.segments.value.last) case _ => Set() } ) + } def invokeCommand( target: Module, diff --git a/main/src/mill/main/ResolveNonEmpty.scala b/main/src/mill/main/ResolveNonEmpty.scala index 11267327816..b0af0374353 100644 --- a/main/src/mill/main/ResolveNonEmpty.scala +++ b/main/src/mill/main/ResolveNonEmpty.scala @@ -15,7 +15,7 @@ object ResolveNonEmpty { ): Either[String, Set[Resolved]] = { ResolveCore.resolve( selector, - ResolveCore.Resolved.Module(current), + ResolveCore.Resolved.Module(current.millModuleSegments, Right(current)), discover, args, Nil diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index ea487e7dc55..b81f6c37db2 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -404,5 +404,18 @@ object ResolversTests extends TestSuite { ) } } + + "moduleInitError" - { + val check = ResolversTests.checkSeq(moduleInitError) _ + "rootTarget" - check( + Seq("rootTarget"), + Right(Set(_.rootTarget)) + ) + "fooTarget" - check( + Seq("fooTarget"), + Left("Foo Boom") + ) + + } } } diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 98bcd43ceea..2fa972b2ac1 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -128,6 +128,29 @@ class TestGraphs() { def right = T { task1() + task2() + left() + 1 } } + + object moduleInitError extends TestUtil.BaseModule{ + def rootTarget = T{ println("Running rootTarget"); "rootTarget" } + def rootCommand(s: String) = T.command{ println(s"Running rootCommand $s") } + + object foo extends Module{ + def fooTarget = T{ println(s"Running fooTarget"); 123 } + def fooCommand(s: String) = T.command{ println(s"Running fooCommand $s") } + throw new Exception("Foo Boom") + } + + object bar extends Module { + def barTarget = T { println(s"Running barTarget"); "abc" } + def barCommand(s: String) = T.command{ println(s"Running barCommand $s") } + + object qux extends Module{ + def quxTarget = T { println(s"Running quxTarget"); "xyz" } + def quxCommand(s: String) = T.command{ println(s"Running quxCommand $s") } + throw new Exception("Qux Boom") + } + } + + } } object TestGraphs { From 1655c5196463ddbb403b1948afa0900baf6f13dd Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 13:52:27 +0800 Subject: [PATCH 28/57] first few module initialization error unit tests pass --- main/src/mill/main/ResolveCore.scala | 4 +-- main/test/src/mill/main/ResolversTests.scala | 33 +++++++++++++++----- main/test/src/mill/util/TestGraphs.scala | 7 +++-- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index dd9d01e6b37..a6af5fcd55a 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -173,7 +173,7 @@ object ResolveCore { val commands = Module .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) .map(m => decode(m.getName)) - .map(name => + .map { name => Resolved.Command( segments ++ Seq(Segment.Label(name)), invokeCommand( @@ -183,7 +183,7 @@ object ResolveCore { args ).head ) - ) + } (modules ++ crosses ++ targets ++ commands).toSet } diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index b81f6c37db2..250c6123128 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -14,8 +14,20 @@ object ResolversTests extends TestSuite { selectorStrings: Seq[String], expected0: Either[String, Set[T => NamedTask[_]]] ) = { - val expected = expected0.map(_.map(_(module))) + checkSeq0[T](module)( + selectorStrings, + resolved => { + resolved.map(_.map(_.toString).toSet[String]) == + expected.map(_.map(_.toString)) + } + ) + } + + def checkSeq0[T <: mill.define.BaseModule](module: T)( + selectorStrings: Seq[String], + check: Either[String, List[NamedTask[_]]] => Boolean + ) = { val resolved = for { task <- mill.main.ResolveTasks.resolve0( None, @@ -25,7 +37,7 @@ object ResolversTests extends TestSuite { ) } yield task - assert(resolved.map(_.map(_.toString).toSet[String]) == expected.map(_.map(_.toString))) + assert(check(resolved)) } val tests = Tests { @@ -406,14 +418,21 @@ object ResolversTests extends TestSuite { } "moduleInitError" - { - val check = ResolversTests.checkSeq(moduleInitError) _ - "rootTarget" - check( + "rootTarget" - ResolversTests.checkSeq(moduleInitError)( Seq("rootTarget"), Right(Set(_.rootTarget)) ) - "fooTarget" - check( - Seq("fooTarget"), - Left("Foo Boom") + "fooTarget" - ResolversTests.checkSeq0(moduleInitError)( + Seq("foo.fooTarget"), + res => res.isLeft && res.left.exists(_.contains("Foo Boom")) + ) + "barTarget" - ResolversTests.checkSeq(moduleInitError)( + Seq("bar.barTarget"), + Right(Set(_.bar.barTarget)) + ) + "quxTarget" - ResolversTests.checkSeq0(moduleInitError)( + Seq("bar.qux.barTarget"), + res => res.isLeft && res.left.exists(_.contains("Qux Boom")) ) } diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 2fa972b2ac1..356474486f1 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -130,7 +130,7 @@ class TestGraphs() { } object moduleInitError extends TestUtil.BaseModule{ - def rootTarget = T{ println("Running rootTarget"); "rootTarget" } + def rootTarget = T{ println("Running rootTarget"); "rootTarget Result" } def rootCommand(s: String) = T.command{ println(s"Running rootCommand $s") } object foo extends Module{ @@ -140,16 +140,17 @@ class TestGraphs() { } object bar extends Module { - def barTarget = T { println(s"Running barTarget"); "abc" } + def barTarget = T { println(s"Running barTarget"); "barTarget Result" } def barCommand(s: String) = T.command{ println(s"Running barCommand $s") } object qux extends Module{ - def quxTarget = T { println(s"Running quxTarget"); "xyz" } + def quxTarget = T { println(s"Running quxTarget"); "quxTarget Result" } def quxCommand(s: String) = T.command{ println(s"Running quxCommand $s") } throw new Exception("Qux Boom") } } + override lazy val millDiscover = Discover[this.type] } } From 9881f0d33045c5e0c5ea3a0e91fcede89c366e69 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 13:56:05 +0800 Subject: [PATCH 29/57] comments --- main/test/src/mill/main/ResolversTests.scala | 38 +++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 250c6123128..af09dcbd758 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -418,20 +418,48 @@ object ResolversTests extends TestSuite { } "moduleInitError" - { - "rootTarget" - ResolversTests.checkSeq(moduleInitError)( + val check = ResolversTests.checkSeq(moduleInitError) _ + val checkCond = ResolversTests.checkSeq0(moduleInitError) _ + // We can resolve the root module tasks even when the + // sub-modules fail to initialize + "rootTarget" - check( Seq("rootTarget"), Right(Set(_.rootTarget)) ) - "fooTarget" - ResolversTests.checkSeq0(moduleInitError)( + "rootCommand" - check( + Seq("rootCommand", "hello"), + Right(Set(_.rootCommand("hello"))) + ) + + // Resolving tasks on a module that fails to initialize is properly + // caught and reported in the Either result + "fooTarget" - checkCond( Seq("foo.fooTarget"), res => res.isLeft && res.left.exists(_.contains("Foo Boom")) ) - "barTarget" - ResolversTests.checkSeq(moduleInitError)( + "fooCommand" - checkCond( + Seq("foo.fooCommand", "hello"), + res => res.isLeft && res.left.exists(_.contains("Foo Boom")) + ) + + // Sub-modules that can initialize allow tasks to be resolved, even + // if their siblings or children are broken + "barTarget" - check( Seq("bar.barTarget"), Right(Set(_.bar.barTarget)) ) - "quxTarget" - ResolversTests.checkSeq0(moduleInitError)( - Seq("bar.qux.barTarget"), + "barCommand" - check( + Seq("bar.barCommand", "hello"), + Right(Set(_.bar.barCommand("hello"))) + ) + + // Nested sub-modules that fail to initialize are properly handled + "quxTarget" - checkCond( + Seq("bar.qux.quxTarget"), + res => res.isLeft && res.left.exists(_.contains("Qux Boom")) + ) + "quxCommand" - checkCond( + Seq("bar.qux.quxCommand", "hello"), res => res.isLeft && res.left.exists(_.contains("Qux Boom")) ) From 0de8d031be2980d5bd432177a685435c3430cc3f Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 13:56:55 +0800 Subject: [PATCH 30/57] . --- main/src/mill/main/ResolveNonEmpty.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/mill/main/ResolveNonEmpty.scala b/main/src/mill/main/ResolveNonEmpty.scala index b0af0374353..6fd81e01746 100644 --- a/main/src/mill/main/ResolveNonEmpty.scala +++ b/main/src/mill/main/ResolveNonEmpty.scala @@ -26,8 +26,8 @@ object ResolveNonEmpty { next match { case Segment.Label(s) => val possibleStrings = possibleNexts.collect { case Segment.Label(s) => s } - errorMsgLabel(s, possibleStrings, segments, Segments(selector)) + case Segment.Cross(keys) => val possibleCrossKeys = possibleNexts.collect { case Segment.Cross(keys) => keys } errorMsgCross(keys, possibleCrossKeys, segments, Segments(selector)) From 296f4ecac42fe3e7f48e8d3e7574037d30dae7ee Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 14:01:08 +0800 Subject: [PATCH 31/57] simple cross module partial initialization failure test --- main/test/src/mill/main/ResolversTests.scala | 96 +++++++++++--------- main/test/src/mill/util/TestGraphs.scala | 10 ++ 2 files changed, 65 insertions(+), 41 deletions(-) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index af09dcbd758..529be6b30d5 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -418,51 +418,65 @@ object ResolversTests extends TestSuite { } "moduleInitError" - { - val check = ResolversTests.checkSeq(moduleInitError) _ - val checkCond = ResolversTests.checkSeq0(moduleInitError) _ - // We can resolve the root module tasks even when the - // sub-modules fail to initialize - "rootTarget" - check( - Seq("rootTarget"), - Right(Set(_.rootTarget)) - ) - "rootCommand" - check( - Seq("rootCommand", "hello"), - Right(Set(_.rootCommand("hello"))) - ) + "simple" - { + val check = ResolversTests.checkSeq(moduleInitError) _ + val checkCond = ResolversTests.checkSeq0(moduleInitError) _ + // We can resolve the root module tasks even when the + // sub-modules fail to initialize + "rootTarget" - check( + Seq("rootTarget"), + Right(Set(_.rootTarget)) + ) + "rootCommand" - check( + Seq("rootCommand", "hello"), + Right(Set(_.rootCommand("hello"))) + ) - // Resolving tasks on a module that fails to initialize is properly - // caught and reported in the Either result - "fooTarget" - checkCond( - Seq("foo.fooTarget"), - res => res.isLeft && res.left.exists(_.contains("Foo Boom")) - ) - "fooCommand" - checkCond( - Seq("foo.fooCommand", "hello"), - res => res.isLeft && res.left.exists(_.contains("Foo Boom")) - ) + // Resolving tasks on a module that fails to initialize is properly + // caught and reported in the Either result + "fooTarget" - checkCond( + Seq("foo.fooTarget"), + res => res.isLeft && res.left.exists(_.contains("Foo Boom")) + ) + "fooCommand" - checkCond( + Seq("foo.fooCommand", "hello"), + res => res.isLeft && res.left.exists(_.contains("Foo Boom")) + ) - // Sub-modules that can initialize allow tasks to be resolved, even - // if their siblings or children are broken - "barTarget" - check( - Seq("bar.barTarget"), - Right(Set(_.bar.barTarget)) - ) - "barCommand" - check( - Seq("bar.barCommand", "hello"), - Right(Set(_.bar.barCommand("hello"))) - ) + // Sub-modules that can initialize allow tasks to be resolved, even + // if their siblings or children are broken + "barTarget" - check( + Seq("bar.barTarget"), + Right(Set(_.bar.barTarget)) + ) + "barCommand" - check( + Seq("bar.barCommand", "hello"), + Right(Set(_.bar.barCommand("hello"))) + ) - // Nested sub-modules that fail to initialize are properly handled - "quxTarget" - checkCond( - Seq("bar.qux.quxTarget"), - res => res.isLeft && res.left.exists(_.contains("Qux Boom")) - ) - "quxCommand" - checkCond( - Seq("bar.qux.quxCommand", "hello"), - res => res.isLeft && res.left.exists(_.contains("Qux Boom")) - ) + // Nested sub-modules that fail to initialize are properly handled + "quxTarget" - checkCond( + Seq("bar.qux.quxTarget"), + res => res.isLeft && res.left.exists(_.contains("Qux Boom")) + ) + "quxCommand" - checkCond( + Seq("bar.qux.quxCommand", "hello"), + res => res.isLeft && res.left.exists(_.contains("Qux Boom")) + ) + } + "cross" - { + val check = ResolversTests.checkSeq(crossModuleInitError) _ + val checkCond = ResolversTests.checkSeq0(crossModuleInitError) _ + test - check( + Seq("myCross[1].foo"), + Right(Set(_.myCross(1).foo)) + ) + test - checkCond( + Seq("myCross[3].foo"), + res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) + ) + } } } } diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 356474486f1..38e65073df2 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -152,6 +152,16 @@ class TestGraphs() { override lazy val millDiscover = Discover[this.type] } + + object crossModuleInitError extends TestUtil.BaseModule{ + object myCross extends Cross[MyCross](1, 2, 3, 4) + trait MyCross extends Cross.Module[Int]{ + if (crossValue > 2) throw new Exception(s"MyCross Boom $crossValue") + def foo = T{ crossValue } + } + + override lazy val millDiscover = Discover[this.type] + } } object TestGraphs { From ca651721d28462c23246e74419b25ed3e47af3fe Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 14:08:44 +0800 Subject: [PATCH 32/57] simple cross module initialization failure --- main/src/mill/main/ResolveCore.scala | 22 +++++++------- main/test/src/mill/main/ResolversTests.scala | 30 +++++++++++++------- main/test/src/mill/util/TestGraphs.scala | 12 +++++++- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index a6af5fcd55a..5b3123bd1f2 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -196,18 +196,20 @@ object ResolveCore { args: Seq[String] ) = { val segments = Segments(revSelectorsSoFar0.reverse) - NotFound( - segments, - Set(current), - next, - current match { - case m: Resolved.Module => - resolveDirectChildren(m.valueOrErr.right.get, None, discover, args, segments) + val possibleNextsOrErr = current match { + case m: Resolved.Module => + m.valueOrErr.map(obj => + resolveDirectChildren(obj, None, discover, args, segments) .map(_.segments.value.last) + ) - case _ => Set() - } - ) + case _ => Right(Set[Segment]()) + } + + possibleNextsOrErr match{ + case Right(nexts) => NotFound(segments, Set(current), next, nexts) + case Left(err) => Error(err) + } } def invokeCommand( diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 529be6b30d5..2561afa385c 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -465,17 +465,25 @@ object ResolversTests extends TestSuite { ) } "cross" - { - val check = ResolversTests.checkSeq(crossModuleInitError) _ - val checkCond = ResolversTests.checkSeq0(crossModuleInitError) _ - test - check( - Seq("myCross[1].foo"), - Right(Set(_.myCross(1).foo)) - ) - test - checkCond( - Seq("myCross[3].foo"), - res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) - ) - + "simple" - { + val checkCond = ResolversTests.checkSeq0(crossModuleSimpleInitError) _ + checkCond( + Seq("myCross[1].foo"), + res => res.isLeft && res.left.exists(_.contains("MyCross Boom")) + ) + } + "partial" - { + val check = ResolversTests.checkSeq(crossModulePartialInitError) _ + val checkCond = ResolversTests.checkSeq0(crossModulePartialInitError) _ + test - check( + Seq("myCross[1].foo"), + Right(Set(_.myCross(1).foo)) + ) + test - checkCond( + Seq("myCross[3].foo"), + res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) + ) + } } } } diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 38e65073df2..4a2f2619c59 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -153,7 +153,17 @@ class TestGraphs() { override lazy val millDiscover = Discover[this.type] } - object crossModuleInitError extends TestUtil.BaseModule{ + object crossModuleSimpleInitError extends TestUtil.BaseModule{ + object myCross extends Cross[MyCross](1, 2, 3, 4){ + throw new Exception(s"MyCross Boom") + } + trait MyCross extends Cross.Module[Int]{ + def foo = T{ crossValue } + } + + override lazy val millDiscover = Discover[this.type] + } + object crossModulePartialInitError extends TestUtil.BaseModule{ object myCross extends Cross[MyCross](1, 2, 3, 4) trait MyCross extends Cross.Module[Int]{ if (crossValue > 2) throw new Exception(s"MyCross Boom $crossValue") From a430a0b74c83cc8614e4d02d725894eb71d12602 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 14:17:40 +0800 Subject: [PATCH 33/57] wip --- main/src/mill/main/MainModule.scala | 1 + main/src/mill/main/ResolveCore.scala | 36 ++++++++++--------- main/test/src/mill/main/MainModuleTests.scala | 1 + main/test/src/mill/main/ResolversTests.scala | 9 +++-- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 5af7477a155..3c56c8e013f 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -339,6 +339,7 @@ trait MainModule extends mill.Module { } } + pprint.log(pathsToRemove) pathsToRemove match { // case Left(err) => // Result.Failure(err) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 5b3123bd1f2..c0364c37ce1 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -49,20 +49,19 @@ object ResolveCore { ) extends Failed case class Error(msg: String) extends Failed - def resolve( - remainingSelector: List[Segment], - current: Resolved, - discover: Discover[_], - args: Seq[String], - revSelectorsSoFar0: List[Segment] - ): Result = remainingSelector match { + def resolve(remainingQuery: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String], + revQuerySoFar0: List[Segment] + ): Result = remainingQuery match { case Nil => Success(Set(current)) case head :: tail => - val revSelectorsSoFar = head :: revSelectorsSoFar0 - val segments = Segments(revSelectorsSoFar.reverse) + val revQuerySoFar = head :: revQuerySoFar0 + val querySegments = Segments(revQuerySoFar0.reverse) def recurse(searchModules: Set[Resolved]): Result = { val (failures, successesLists) = searchModules - .map(resolve(tail, _, discover, args, revSelectorsSoFar)) + .map(resolve(tail, _, discover, args, revQuerySoFar)) .partitionMap { case s: Success => Right(s.value); case f: Failed => Left(f) } val (errors, notFounds) = failures.partitionMap { @@ -74,7 +73,7 @@ object ResolveCore { else if (successesLists.flatten.nonEmpty) Success(successesLists.flatten) else notFounds.size match { case 1 => notFounds.head - case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) + case _ => notFoundResult(revQuerySoFar0, current, head, discover, args) } } @@ -94,8 +93,10 @@ object ResolveCore { resolveDirectChildren(m, None, discover, args, m.millModuleSegments) ) ) - case "_" => Right(resolveDirectChildren(obj, None, discover, args, segments)) - case _ => Right(resolveDirectChildren(obj, Some(singleLabel), discover, args, segments)) + case "_" => + Right(resolveDirectChildren(obj, None, discover, args, querySegments)) + case _ => + Right(resolveDirectChildren(obj, Some(singleLabel), discover, args, querySegments)) } } @@ -118,7 +119,7 @@ object ResolveCore { recurse(searchModules.map(m => Resolved.Module(m.millModuleSegments, Right(m))).toSet) - case _ => notFoundResult(revSelectorsSoFar0, current, head, discover, args) + case _ => notFoundResult(revQuerySoFar0, current, head, discover, args) } } @@ -163,12 +164,13 @@ object ResolveCore { val targets = Module .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) - .map(m => + .map { m => + pprint.log(segments.render) + pprint.log((segments ++ Seq(Segment.Label(decode(m.getName)))).render) Resolved.Target( segments ++ Seq(Segment.Label(decode(m.getName))), catchReflectException(m.invoke(obj).asInstanceOf[Target[_]])) - - ) + } val commands = Module .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) diff --git a/main/test/src/mill/main/MainModuleTests.scala b/main/test/src/mill/main/MainModuleTests.scala index aca9b114add..4724d20e788 100644 --- a/main/test/src/mill/main/MainModuleTests.scala +++ b/main/test/src/mill/main/MainModuleTests.scala @@ -165,6 +165,7 @@ object MainModuleTests extends TestSuite { os.sub / "bar" / "target.dest" / "dummy.txt" ) + println("\n\nCleaning foo.target\n\n") val r2 = ev.evaluator.evaluate(Agg(cleanModule.clean(ev.evaluator, "foo.target"))) assert(r2.failing.keyCount == 0) checkExists(false)( diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 2561afa385c..400c5303ac7 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -473,11 +473,14 @@ object ResolversTests extends TestSuite { ) } "partial" - { - val check = ResolversTests.checkSeq(crossModulePartialInitError) _ val checkCond = ResolversTests.checkSeq0(crossModulePartialInitError) _ - test - check( + + // If any one of the cross modules fails to initialize, even if it's + // not the one you are asking for, we fail and make sure to catch and + // handle the error + test - checkCond( Seq("myCross[1].foo"), - Right(Set(_.myCross(1).foo)) + res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) ) test - checkCond( Seq("myCross[3].foo"), From 2c570756680b7854558422480e8242e1b9a42283 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 14:30:55 +0800 Subject: [PATCH 34/57] add ResolveMetadata testing to ResolversTests.scala --- main/src/mill/main/ResolveCore.scala | 2 - main/test/src/mill/main/ResolversTests.scala | 293 +++++++++++++------ 2 files changed, 196 insertions(+), 99 deletions(-) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index c0364c37ce1..99ebaf0018e 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -165,8 +165,6 @@ object ResolveCore { val targets = Module .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) .map { m => - pprint.log(segments.render) - pprint.log((segments ++ Seq(Segment.Label(decode(m.getName)))).render) Resolved.Target( segments ++ Seq(Segment.Label(decode(m.getName))), catchReflectException(m.invoke(obj).asInstanceOf[Target[_]])) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 400c5303ac7..0f58759cd6e 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -5,47 +5,68 @@ import mill.util.TestGraphs._ import utest._ object ResolversTests extends TestSuite { - def check[T <: mill.define.BaseModule](module: T)( - selectorString: String, - expected0: Either[String, Set[T => NamedTask[_]]] - ) = checkSeq(module)(Seq(selectorString), expected0) + class Checker[T <: mill.define.BaseModule](module: T) { - def checkSeq[T <: mill.define.BaseModule](module: T)( - selectorStrings: Seq[String], - expected0: Either[String, Set[T => NamedTask[_]]] - ) = { - val expected = expected0.map(_.map(_(module))) - checkSeq0[T](module)( - selectorStrings, - resolved => { - resolved.map(_.map(_.toString).toSet[String]) == - expected.map(_.map(_.toString)) - } - ) - } + def apply( + selectorString: String, + expected0: Either[String, Set[T => NamedTask[_]]], + expectedMetadata: Set[String] = Set() + ) = checkSeq(Seq(selectorString), expected0, expectedMetadata) - def checkSeq0[T <: mill.define.BaseModule](module: T)( - selectorStrings: Seq[String], - check: Either[String, List[NamedTask[_]]] => Boolean - ) = { - val resolved = for { - task <- mill.main.ResolveTasks.resolve0( - None, - module, + def checkSeq( + selectorStrings: Seq[String], + expected0: Either[String, Set[T => NamedTask[_]]], + expectedMetadata: Set[String] = Set() + ) = { + val expected = expected0.map(_.map(_(module))) + checkSeq0( selectorStrings, - SelectMode.Separated + resolvedTasks => { + resolvedTasks.map(_.map(_.toString).toSet[String]) == + expected.map(_.map(_.toString)) + }, + resolvedMetadata => { + expectedMetadata.isEmpty || + resolvedMetadata.map(_.toSet) == Right(expectedMetadata) + } ) - } yield task + } + + def checkSeq0( + selectorStrings: Seq[String], + check: Either[String, List[NamedTask[_]]] => Boolean, + checkMetadata: Either[String, List[String]] => Boolean = _ => true + ) = { + val resolved = for { + task <- mill.main.ResolveTasks.resolve0( + None, + module, + selectorStrings, + SelectMode.Separated + ) + } yield task - assert(check(resolved)) + val resolvedMetadata = for { + task <- mill.main.ResolveMetadata.resolve0( + None, + module, + selectorStrings, + SelectMode.Separated + ) + } yield task + + assert(check(resolved)) + assert(checkMetadata(resolvedMetadata)) + } } val tests = Tests { val graphs = new mill.util.TestGraphs() import graphs._ "single" - { - val check = ResolversTests.check(singleton) _ - "pos" - check("single", Right(Set(_.single))) + val check = new Checker(singleton) + "pos" - check("single", Right(Set(_.single)), Set("single")) + "wildcard" - check("_", Right(Set(_.single)), Set("single")) "neg1" - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) "neg2" - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) "neg3" - check("nsiigle", Left("Cannot resolve nsiigle. Did you mean single?")) @@ -64,9 +85,9 @@ object ResolversTests extends TestSuite { "neg7" - check("", Left("Selector cannot be empty")) } "backtickIdentifiers" - { - val check = ResolversTests.check(bactickIdentifiers) _ - "pos1" - check("up-target", Right(Set(_.`up-target`))) - "pos2" - check("a-down-target", Right(Set(_.`a-down-target`))) + val check = new Checker(bactickIdentifiers) + "pos1" - check("up-target", Right(Set(_.`up-target`)), Set("up-target")) + "pos2" - check("a-down-target", Right(Set(_.`a-down-target`)), Set("a-down-target")) "neg1" - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) "neg2" - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) "neg3" - check( @@ -83,7 +104,7 @@ object ResolversTests extends TestSuite { Left("Parsing exception Position 1:10, found \"&\"") ) "nested" - { - "pos" - check("nested-module.nested-target", Right(Set(_.`nested-module`.`nested-target`))) + "pos" - check("nested-module.nested-target", Right(Set(_.`nested-module`.`nested-target`)), Set("nested-module.nested-target")) "neg" - check( "nested-module.doesntExist", Left( @@ -93,25 +114,29 @@ object ResolversTests extends TestSuite { } } "nested" - { - val check = ResolversTests.check(nestedModule) _ - "pos1" - check("single", Right(Set(_.single))) - "pos2" - check("nested.single", Right(Set(_.nested.single))) - "pos3" - check("classInstance.single", Right(Set(_.classInstance.single))) + val check = new Checker(nestedModule) + "pos1" - check("single", Right(Set(_.single)), Set("single")) + "pos2" - check("nested.single", Right(Set(_.nested.single)), Set("nested.single")) + "pos3" - check("classInstance.single", Right(Set(_.classInstance.single)), Set("classInstance.single")) "posCurly1" - check( "{nested,classInstance}.single", - Right(Set(_.nested.single, _.classInstance.single)) + Right(Set(_.nested.single, _.classInstance.single)), + Set("nested.single", "classInstance.single") ) "posCurly2" - check( "{single,{nested,classInstance}.single}", - Right(Set(_.single, _.nested.single, _.classInstance.single)) + Right(Set(_.single, _.nested.single, _.classInstance.single)), + Set("single", "nested.single", "classInstance.single") ) "posCurly3" - check( "{single,nested.single,classInstance.single}", - Right(Set(_.single, _.nested.single, _.classInstance.single)) + Right(Set(_.single, _.nested.single, _.classInstance.single)), + Set("single", "nested.single", "classInstance.single") ) "posCurly4" - check( "{,nested.,classInstance.}single", - Right(Set(_.single, _.nested.single, _.classInstance.single)) + Right(Set(_.single, _.nested.single, _.classInstance.single)), + Set("single", "nested.single", "classInstance.single") ) "neg1" - check( "doesntExist", @@ -142,7 +167,8 @@ object ResolversTests extends TestSuite { Right(Set( _.classInstance.single, _.nested.single - )) + )), + Set("nested.single", "classInstance.single") ) "wildcardNeg" - check( "_._.single", @@ -162,7 +188,8 @@ object ResolversTests extends TestSuite { _.single, _.classInstance.single, _.nested.single - )) + )), + Set("single", "nested.single", "classInstance.single") ) "wildcard3" - check( @@ -170,14 +197,15 @@ object ResolversTests extends TestSuite { Right(Set( _.classInstance.single, _.nested.single - )) + )), + Set("nested.single", "classInstance.single") ) } "doubleNested" - { - val check = ResolversTests.check(doubleNestedModule) _ - "pos1" - check("single", Right(Set(_.single))) - "pos2" - check("nested.single", Right(Set(_.nested.single))) - "pos3" - check("nested.inner.single", Right(Set(_.nested.inner.single))) + val check = new Checker(doubleNestedModule) + "pos1" - check("single", Right(Set(_.single)), Set("single")) + "pos2" - check("nested.single", Right(Set(_.nested.single)), Set("nested.single")) + "pos3" - check("nested.inner.single", Right(Set(_.nested.inner.single)), Set("nested.inner.single")) "neg1" - check( "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") @@ -204,12 +232,13 @@ object ResolversTests extends TestSuite { "cross" - { "single" - { - val check = ResolversTests.check(singleCross) _ - "pos1" - check("cross[210].suffix", Right(Set(_.cross("210").suffix))) - "pos2" - check("cross[211].suffix", Right(Set(_.cross("211").suffix))) + val check = new Checker(singleCross) + "pos1" - check("cross[210].suffix", Right(Set(_.cross("210").suffix)), Set("cross[210].suffix")) + "pos2" - check("cross[211].suffix", Right(Set(_.cross("211").suffix)), Set("cross[211].suffix")) "posCurly" - check( "cross[{210,211}].suffix", - Right(Set(_.cross("210").suffix, _.cross("211").suffix)) + Right(Set(_.cross("210").suffix, _.cross("211").suffix)), + Set("cross[210].suffix", "cross[211].suffix") ) "neg1" - check( "cross[210].doesntExist", @@ -239,7 +268,8 @@ object ResolversTests extends TestSuite { _.cross("210").suffix, _.cross("211").suffix, _.cross("212").suffix - )) + )), + Set("cross[210].suffix", "cross[211].suffix", "cross[212].suffix") ) "wildcard2" - check( "cross[__].suffix", @@ -247,18 +277,21 @@ object ResolversTests extends TestSuite { _.cross("210").suffix, _.cross("211").suffix, _.cross("212").suffix - )) + )), + Set("cross[210].suffix", "cross[211].suffix", "cross[212].suffix") ) } "double" - { - val check = ResolversTests.check(doubleCross) _ + val check = new Checker(doubleCross) "pos1" - check( "cross[210,jvm].suffix", - Right(Set(_.cross("210", "jvm").suffix)) + Right(Set(_.cross("210", "jvm").suffix)), + Set("cross[210,jvm].suffix") ) "pos2" - check( "cross[211,jvm].suffix", - Right(Set(_.cross("211", "jvm").suffix)) + Right(Set(_.cross("211", "jvm").suffix)), + Set("cross[211,jvm].suffix") ) "wildcard" - { "labelNeg1" - check( @@ -297,7 +330,16 @@ object ResolversTests extends TestSuite { _.cross("212", "jvm").suffix, _.cross("212", "js").suffix, _.cross("212", "native").suffix - )) + )), + Set( + "cross[210,jvm].suffix", + "cross[210,js].suffix", + "cross[211,jvm].suffix", + "cross[211,js].suffix", + "cross[212,jvm].suffix", + "cross[212,js].suffix", + "cross[212,native].suffix", + ) ) "first" - check( "cross[_,jvm].suffix", @@ -305,14 +347,23 @@ object ResolversTests extends TestSuite { _.cross("210", "jvm").suffix, _.cross("211", "jvm").suffix, _.cross("212", "jvm").suffix - )) + )), + Set( + "cross[210,jvm].suffix", + "cross[211,jvm].suffix", + "cross[212,jvm].suffix", + ) ) "second" - check( "cross[210,_].suffix", Right(Set( _.cross("210", "jvm").suffix, _.cross("210", "js").suffix - )) + )), + Set( + "cross[210,jvm].suffix", + "cross[210,js].suffix" + ) ) "both" - check( "cross[_,_].suffix", @@ -324,7 +375,17 @@ object ResolversTests extends TestSuite { _.cross("212", "jvm").suffix, _.cross("212", "js").suffix, _.cross("212", "native").suffix - )) + )), + Set( + "cross[210,jvm].suffix", + "cross[210,js].suffix", + "cross[211,jvm].suffix", + "cross[211,js].suffix", + "cross[212,jvm].suffix", + "cross[212,js].suffix", + "cross[212,js].suffix", + "cross[212,native].suffix", + ) ) "both2" - check( "cross[__].suffix", @@ -336,19 +397,31 @@ object ResolversTests extends TestSuite { _.cross("212", "jvm").suffix, _.cross("212", "js").suffix, _.cross("212", "native").suffix - )) + )), + Set( + "cross[210,jvm].suffix", + "cross[210,js].suffix", + "cross[211,jvm].suffix", + "cross[211,js].suffix", + "cross[212,jvm].suffix", + "cross[212,js].suffix", + "cross[212,js].suffix", + "cross[212,native].suffix", + ) ) } } "nested" - { - val check = ResolversTests.check(nestedCrosses) _ + val check = new Checker(nestedCrosses) "pos1" - check( "cross[210].cross2[js].suffix", - Right(Set(_.cross("210").cross2("js").suffix)) + Right(Set(_.cross("210").cross2("js").suffix)), + Set("cross[210].cross2[js].suffix") ) "pos2" - check( "cross[211].cross2[jvm].suffix", - Right(Set(_.cross("211").cross2("jvm").suffix)) + Right(Set(_.cross("211").cross2("jvm").suffix)), + Set("cross[211].cross2[jvm].suffix") ) "pos2NoDefaultTask" - check( "cross[211].cross2[jvm]", @@ -361,7 +434,12 @@ object ResolversTests extends TestSuite { _.cross("210").cross2("jvm").suffix, _.cross("211").cross2("jvm").suffix, _.cross("212").cross2("jvm").suffix - )) + )), + Set( + "cross[210].cross2[jvm].suffix", + "cross[211].cross2[jvm].suffix", + "cross[212].cross2[jvm].suffix" + ) ) "second" - check( "cross[210].cross2[_].suffix", @@ -369,7 +447,12 @@ object ResolversTests extends TestSuite { _.cross("210").cross2("jvm").suffix, _.cross("210").cross2("js").suffix, _.cross("210").cross2("native").suffix - )) + )), + Set( + "cross[210].cross2[jvm].suffix", + "cross[210].cross2[js].suffix", + "cross[210].cross2[native].suffix" + ) ) "both" - check( "cross[_].cross2[_].suffix", @@ -383,106 +466,122 @@ object ResolversTests extends TestSuite { _.cross("212").cross2("jvm").suffix, _.cross("212").cross2("js").suffix, _.cross("212").cross2("native").suffix - )) + )), + Set( + "cross[210].cross2[jvm].suffix", + "cross[210].cross2[js].suffix", + "cross[210].cross2[native].suffix", + "cross[211].cross2[jvm].suffix", + "cross[211].cross2[js].suffix", + "cross[211].cross2[native].suffix", + "cross[212].cross2[jvm].suffix", + "cross[213].cross2[js].suffix", + "cross[214].cross2[native].suffix" + ) ) } } "nestedCrossTaskModule" - { - val check = ResolversTests.checkSeq(nestedTaskCrosses) _ - "pos1" - check( + val check = new Checker(nestedTaskCrosses) + "pos1" - check.checkSeq( Seq("cross1[210].cross2[js].suffixCmd"), - Right(Set(_.cross1("210").cross2("js").suffixCmd())) + Right(Set(_.cross1("210").cross2("js").suffixCmd())), + Set("cross1[210].cross2[js].suffixCmd"), ) - "pos1Default" - check( + "pos1Default" - check.checkSeq( Seq("cross1[210].cross2[js]"), - Right(Set(_.cross1("210").cross2("js").suffixCmd())) + Right(Set(_.cross1("210").cross2("js").suffixCmd())), + Set("cross1[210].cross2[js]]") ) - "pos1WithWildcard" - check( + "pos1WithWildcard" - check.checkSeq( Seq("cross1[210].cross2[js]._"), - Right(Set(_.cross1("210").cross2("js").suffixCmd())) + Right(Set(_.cross1("210").cross2("js").suffixCmd())), + Set("cross1[210].cross2[js].suffixCmd") ) - "pos1WithArgs" - check( + "pos1WithArgs" - check.checkSeq( Seq("cross1[210].cross2[js].suffixCmd", "suffix-arg"), - Right(Set(_.cross1("210").cross2("js").suffixCmd("suffix-arg"))) + Right(Set(_.cross1("210").cross2("js").suffixCmd("suffix-arg"))), + Set("cross1[210].cross2[js].suffixCmd") ) - "pos2" - check( + "pos2" - check.checkSeq( Seq("cross1[211].cross2[jvm].suffixCmd"), - Right(Set(_.cross1("211").cross2("jvm").suffixCmd())) + Right(Set(_.cross1("211").cross2("jvm").suffixCmd())), + Set("cross1[211].cross2[jvm].suffixCmd") ) - "pos2Default" - check( + "pos2Default" - check.checkSeq( Seq("cross1[211].cross2[jvm]"), - Right(Set(_.cross1("211").cross2("jvm").suffixCmd())) + Right(Set(_.cross1("211").cross2("jvm").suffixCmd())), + Set("cross1[211].cross2[jvm]") ) } } "moduleInitError" - { "simple" - { - val check = ResolversTests.checkSeq(moduleInitError) _ - val checkCond = ResolversTests.checkSeq0(moduleInitError) _ + val check = new Checker(moduleInitError) // We can resolve the root module tasks even when the // sub-modules fail to initialize - "rootTarget" - check( + "rootTarget" - check.checkSeq( Seq("rootTarget"), Right(Set(_.rootTarget)) ) - "rootCommand" - check( + "rootCommand" - check.checkSeq( Seq("rootCommand", "hello"), Right(Set(_.rootCommand("hello"))) ) // Resolving tasks on a module that fails to initialize is properly // caught and reported in the Either result - "fooTarget" - checkCond( + "fooTarget" - check.checkSeq0( Seq("foo.fooTarget"), res => res.isLeft && res.left.exists(_.contains("Foo Boom")) ) - "fooCommand" - checkCond( + "fooCommand" - check.checkSeq0( Seq("foo.fooCommand", "hello"), res => res.isLeft && res.left.exists(_.contains("Foo Boom")) ) // Sub-modules that can initialize allow tasks to be resolved, even // if their siblings or children are broken - "barTarget" - check( + "barTarget" - check.checkSeq( Seq("bar.barTarget"), Right(Set(_.bar.barTarget)) ) - "barCommand" - check( + "barCommand" - check.checkSeq( Seq("bar.barCommand", "hello"), Right(Set(_.bar.barCommand("hello"))) ) // Nested sub-modules that fail to initialize are properly handled - "quxTarget" - checkCond( + "quxTarget" - check.checkSeq0( Seq("bar.qux.quxTarget"), res => res.isLeft && res.left.exists(_.contains("Qux Boom")) ) - "quxCommand" - checkCond( + "quxCommand" - check.checkSeq0( Seq("bar.qux.quxCommand", "hello"), res => res.isLeft && res.left.exists(_.contains("Qux Boom")) ) } "cross" - { "simple" - { - val checkCond = ResolversTests.checkSeq0(crossModuleSimpleInitError) _ - checkCond( + val check = new Checker(crossModuleSimpleInitError) + check.checkSeq0( Seq("myCross[1].foo"), res => res.isLeft && res.left.exists(_.contains("MyCross Boom")) ) } "partial" - { - val checkCond = ResolversTests.checkSeq0(crossModulePartialInitError) _ + val check = new Checker(crossModulePartialInitError) // If any one of the cross modules fails to initialize, even if it's // not the one you are asking for, we fail and make sure to catch and // handle the error - test - checkCond( + test - check.checkSeq0( Seq("myCross[1].foo"), res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) ) - test - checkCond( + test - check.checkSeq0( Seq("myCross[3].foo"), res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) ) From 2ee6597eb82f72504459d020d7525d8e03967df4 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 14:32:52 +0800 Subject: [PATCH 35/57] cleanup --- main/test/src/mill/main/ResolversTests.scala | 55 ++++++++++---------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 0f58759cd6e..a49aeb17cf4 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -19,16 +19,14 @@ object ResolversTests extends TestSuite { expectedMetadata: Set[String] = Set() ) = { val expected = expected0.map(_.map(_(module))) - checkSeq0( - selectorStrings, - resolvedTasks => { - resolvedTasks.map(_.map(_.toString).toSet[String]) == - expected.map(_.map(_.toString)) - }, - resolvedMetadata => { - expectedMetadata.isEmpty || + val (resolvedTasks, resolvedMetadata) = resolveTasksAndMetadata(selectorStrings) + assert( + resolvedTasks.map(_.map(_.toString).toSet[String]) == + expected.map(_.map(_.toString)) + ) + assert( + expectedMetadata.isEmpty || resolvedMetadata.map(_.toSet) == Right(expectedMetadata) - } ) } @@ -37,29 +35,32 @@ object ResolversTests extends TestSuite { check: Either[String, List[NamedTask[_]]] => Boolean, checkMetadata: Either[String, List[String]] => Boolean = _ => true ) = { - val resolved = for { - task <- mill.main.ResolveTasks.resolve0( - None, - module, - selectorStrings, - SelectMode.Separated - ) - } yield task - - val resolvedMetadata = for { - task <- mill.main.ResolveMetadata.resolve0( - None, - module, - selectorStrings, - SelectMode.Separated - ) - } yield task - assert(check(resolved)) + val (resolvedTasks, resolvedMetadata) = resolveTasksAndMetadata(selectorStrings) + assert(check(resolvedTasks)) assert(checkMetadata(resolvedMetadata)) } + + def resolveTasksAndMetadata(selectorStrings: Seq[String]) = { + val resolvedTasks = mill.main.ResolveTasks.resolve0( + None, + module, + selectorStrings, + SelectMode.Separated + ) + + val resolvedMetadata = mill.main.ResolveMetadata.resolve0( + None, + module, + selectorStrings, + SelectMode.Separated + ) + + (resolvedTasks, resolvedMetadata) + } } + val tests = Tests { val graphs = new mill.util.TestGraphs() import graphs._ From e2bb92fba36a5c62f35fd56565afeb5fff80d4ea Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 14:33:22 +0800 Subject: [PATCH 36/57] scalafmt --- main/core/src/mill/define/ExpandBraces.scala | 3 +- main/core/src/mill/define/Module.scala | 6 +- main/src/mill/main/ResolveCore.scala | 37 +++++------ main/test/src/mill/main/ResolversTests.scala | 65 +++++++++++++------- main/test/src/mill/util/TestGraphs.scala | 44 ++++++------- 5 files changed, 85 insertions(+), 70 deletions(-) diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala index e6de9e64de6..73af33e2c53 100644 --- a/main/core/src/mill/define/ExpandBraces.scala +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -2,7 +2,7 @@ package mill.define import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit -object ExpandBraces{ +object ExpandBraces { private sealed trait Fragment private object Fragment { case class Keep(value: String) extends Fragment @@ -42,7 +42,6 @@ object ExpandBraces{ private def plainChars[_p: P]: P[Fragment.Keep] = P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep) - private def emptyExpansionBranch[_p: P] = P("").map(_ => List(Fragment.Keep(""))) private def toExpand[_p: P]: P[Fragment] = diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 808265ab636..f425d57aa46 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -134,15 +134,15 @@ object Module { .flatMap { c => c.getSimpleName match { case s"$name$$" if filter(name) => - c.getFields.find(_.getName == "MODULE$").map(f => (name, () => f.get(c).asInstanceOf[T])) + c.getFields.find(_.getName == "MODULE$").map(f => + (name, () => f.get(c).asInstanceOf[T]) + ) case _ => None } } .distinct - - first ++ second } } diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 99ebaf0018e..077a1756909 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -23,16 +23,13 @@ object ResolveCore { } object Resolved { - case class Module(segments: Segments, - valueOrErr: Either[String, mill.define.Module]) - extends Resolved + case class Module(segments: Segments, valueOrErr: Either[String, mill.define.Module]) + extends Resolved - case class Target(segments: Segments, - valueOrErr: Either[String, mill.define.Target[_]]) - extends Resolved + case class Target(segments: Segments, valueOrErr: Either[String, mill.define.Target[_]]) + extends Resolved - case class Command(segments: Segments, - valueOrErr: Either[String, mill.define.Command[_]]) + case class Command(segments: Segments, valueOrErr: Either[String, mill.define.Command[_]]) extends Resolved } @@ -49,11 +46,12 @@ object ResolveCore { ) extends Failed case class Error(msg: String) extends Failed - def resolve(remainingQuery: List[Segment], - current: Resolved, - discover: Discover[_], - args: Seq[String], - revQuerySoFar0: List[Segment] + def resolve( + remainingQuery: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String], + revQuerySoFar0: List[Segment] ): Result = remainingQuery match { case Nil => Success(Set(current)) case head :: tail => @@ -79,7 +77,6 @@ object ResolveCore { (head, current) match { case (Segment.Label(singleLabel), m: Resolved.Module) => - val resOrErr = m.valueOrErr.flatMap { obj => singleLabel match { case "__" => @@ -90,7 +87,7 @@ object ResolveCore { ).map( _.flatMap(m => Seq(Resolved.Module(m.millModuleSegments, Right(m))) ++ - resolveDirectChildren(m, None, discover, args, m.millModuleSegments) + resolveDirectChildren(m, None, discover, args, m.millModuleSegments) ) ) case "_" => @@ -100,12 +97,11 @@ object ResolveCore { } } - resOrErr match{ + resOrErr match { case Left(err) => Error(err) case Right(res) => recurse(res.toSet) } - case (Segment.Cross(cross), Resolved.Module(_, Right(c: Cross[_]))) => val searchModules: Seq[Module] = if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v @@ -144,7 +140,7 @@ object ResolveCore { val modules = obj .millInternal .reflectNestedObjects0[Module](namePred) - .map{case (name, f) => + .map { case (name, f) => Resolved.Module( segments ++ Seq(Segment.Label(name)), catchReflectException(f()) @@ -167,7 +163,8 @@ object ResolveCore { .map { m => Resolved.Target( segments ++ Seq(Segment.Label(decode(m.getName))), - catchReflectException(m.invoke(obj).asInstanceOf[Target[_]])) + catchReflectException(m.invoke(obj).asInstanceOf[Target[_]]) + ) } val commands = Module @@ -206,7 +203,7 @@ object ResolveCore { case _ => Right(Set[Segment]()) } - possibleNextsOrErr match{ + possibleNextsOrErr match { case Right(nexts) => NotFound(segments, Set(current), next, nexts) case Left(err) => Error(err) } diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index a49aeb17cf4..a89ea1321a9 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -8,16 +8,16 @@ object ResolversTests extends TestSuite { class Checker[T <: mill.define.BaseModule](module: T) { def apply( - selectorString: String, - expected0: Either[String, Set[T => NamedTask[_]]], - expectedMetadata: Set[String] = Set() - ) = checkSeq(Seq(selectorString), expected0, expectedMetadata) + selectorString: String, + expected0: Either[String, Set[T => NamedTask[_]]], + expectedMetadata: Set[String] = Set() + ) = checkSeq(Seq(selectorString), expected0, expectedMetadata) def checkSeq( - selectorStrings: Seq[String], - expected0: Either[String, Set[T => NamedTask[_]]], - expectedMetadata: Set[String] = Set() - ) = { + selectorStrings: Seq[String], + expected0: Either[String, Set[T => NamedTask[_]]], + expectedMetadata: Set[String] = Set() + ) = { val expected = expected0.map(_.map(_(module))) val (resolvedTasks, resolvedMetadata) = resolveTasksAndMetadata(selectorStrings) assert( @@ -31,10 +31,10 @@ object ResolversTests extends TestSuite { } def checkSeq0( - selectorStrings: Seq[String], - check: Either[String, List[NamedTask[_]]] => Boolean, - checkMetadata: Either[String, List[String]] => Boolean = _ => true - ) = { + selectorStrings: Seq[String], + check: Either[String, List[NamedTask[_]]] => Boolean, + checkMetadata: Either[String, List[String]] => Boolean = _ => true + ) = { val (resolvedTasks, resolvedMetadata) = resolveTasksAndMetadata(selectorStrings) assert(check(resolvedTasks)) @@ -60,7 +60,6 @@ object ResolversTests extends TestSuite { } } - val tests = Tests { val graphs = new mill.util.TestGraphs() import graphs._ @@ -105,7 +104,11 @@ object ResolversTests extends TestSuite { Left("Parsing exception Position 1:10, found \"&\"") ) "nested" - { - "pos" - check("nested-module.nested-target", Right(Set(_.`nested-module`.`nested-target`)), Set("nested-module.nested-target")) + "pos" - check( + "nested-module.nested-target", + Right(Set(_.`nested-module`.`nested-target`)), + Set("nested-module.nested-target") + ) "neg" - check( "nested-module.doesntExist", Left( @@ -118,7 +121,11 @@ object ResolversTests extends TestSuite { val check = new Checker(nestedModule) "pos1" - check("single", Right(Set(_.single)), Set("single")) "pos2" - check("nested.single", Right(Set(_.nested.single)), Set("nested.single")) - "pos3" - check("classInstance.single", Right(Set(_.classInstance.single)), Set("classInstance.single")) + "pos3" - check( + "classInstance.single", + Right(Set(_.classInstance.single)), + Set("classInstance.single") + ) "posCurly1" - check( "{nested,classInstance}.single", Right(Set(_.nested.single, _.classInstance.single)), @@ -206,7 +213,11 @@ object ResolversTests extends TestSuite { val check = new Checker(doubleNestedModule) "pos1" - check("single", Right(Set(_.single)), Set("single")) "pos2" - check("nested.single", Right(Set(_.nested.single)), Set("nested.single")) - "pos3" - check("nested.inner.single", Right(Set(_.nested.inner.single)), Set("nested.inner.single")) + "pos3" - check( + "nested.inner.single", + Right(Set(_.nested.inner.single)), + Set("nested.inner.single") + ) "neg1" - check( "doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") @@ -234,8 +245,16 @@ object ResolversTests extends TestSuite { "cross" - { "single" - { val check = new Checker(singleCross) - "pos1" - check("cross[210].suffix", Right(Set(_.cross("210").suffix)), Set("cross[210].suffix")) - "pos2" - check("cross[211].suffix", Right(Set(_.cross("211").suffix)), Set("cross[211].suffix")) + "pos1" - check( + "cross[210].suffix", + Right(Set(_.cross("210").suffix)), + Set("cross[210].suffix") + ) + "pos2" - check( + "cross[211].suffix", + Right(Set(_.cross("211").suffix)), + Set("cross[211].suffix") + ) "posCurly" - check( "cross[{210,211}].suffix", Right(Set(_.cross("210").suffix, _.cross("211").suffix)), @@ -339,7 +358,7 @@ object ResolversTests extends TestSuite { "cross[211,js].suffix", "cross[212,jvm].suffix", "cross[212,js].suffix", - "cross[212,native].suffix", + "cross[212,native].suffix" ) ) "first" - check( @@ -352,7 +371,7 @@ object ResolversTests extends TestSuite { Set( "cross[210,jvm].suffix", "cross[211,jvm].suffix", - "cross[212,jvm].suffix", + "cross[212,jvm].suffix" ) ) "second" - check( @@ -385,7 +404,7 @@ object ResolversTests extends TestSuite { "cross[212,jvm].suffix", "cross[212,js].suffix", "cross[212,js].suffix", - "cross[212,native].suffix", + "cross[212,native].suffix" ) ) "both2" - check( @@ -407,7 +426,7 @@ object ResolversTests extends TestSuite { "cross[212,jvm].suffix", "cross[212,js].suffix", "cross[212,js].suffix", - "cross[212,native].suffix", + "cross[212,native].suffix" ) ) } @@ -488,7 +507,7 @@ object ResolversTests extends TestSuite { "pos1" - check.checkSeq( Seq("cross1[210].cross2[js].suffixCmd"), Right(Set(_.cross1("210").cross2("js").suffixCmd())), - Set("cross1[210].cross2[js].suffixCmd"), + Set("cross1[210].cross2[js].suffixCmd") ) "pos1Default" - check.checkSeq( Seq("cross1[210].cross2[js]"), diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 4a2f2619c59..51574134f3d 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -129,23 +129,23 @@ class TestGraphs() { } - object moduleInitError extends TestUtil.BaseModule{ - def rootTarget = T{ println("Running rootTarget"); "rootTarget Result" } - def rootCommand(s: String) = T.command{ println(s"Running rootCommand $s") } + object moduleInitError extends TestUtil.BaseModule { + def rootTarget = T { println("Running rootTarget"); "rootTarget Result" } + def rootCommand(s: String) = T.command { println(s"Running rootCommand $s") } - object foo extends Module{ - def fooTarget = T{ println(s"Running fooTarget"); 123 } - def fooCommand(s: String) = T.command{ println(s"Running fooCommand $s") } + object foo extends Module { + def fooTarget = T { println(s"Running fooTarget"); 123 } + def fooCommand(s: String) = T.command { println(s"Running fooCommand $s") } throw new Exception("Foo Boom") } object bar extends Module { def barTarget = T { println(s"Running barTarget"); "barTarget Result" } - def barCommand(s: String) = T.command{ println(s"Running barCommand $s") } + def barCommand(s: String) = T.command { println(s"Running barCommand $s") } - object qux extends Module{ + object qux extends Module { def quxTarget = T { println(s"Running quxTarget"); "quxTarget Result" } - def quxCommand(s: String) = T.command{ println(s"Running quxCommand $s") } + def quxCommand(s: String) = T.command { println(s"Running quxCommand $s") } throw new Exception("Qux Boom") } } @@ -153,22 +153,22 @@ class TestGraphs() { override lazy val millDiscover = Discover[this.type] } - object crossModuleSimpleInitError extends TestUtil.BaseModule{ - object myCross extends Cross[MyCross](1, 2, 3, 4){ - throw new Exception(s"MyCross Boom") - } - trait MyCross extends Cross.Module[Int]{ - def foo = T{ crossValue } - } + object crossModuleSimpleInitError extends TestUtil.BaseModule { + object myCross extends Cross[MyCross](1, 2, 3, 4) { + throw new Exception(s"MyCross Boom") + } + trait MyCross extends Cross.Module[Int] { + def foo = T { crossValue } + } override lazy val millDiscover = Discover[this.type] } - object crossModulePartialInitError extends TestUtil.BaseModule{ - object myCross extends Cross[MyCross](1, 2, 3, 4) - trait MyCross extends Cross.Module[Int]{ - if (crossValue > 2) throw new Exception(s"MyCross Boom $crossValue") - def foo = T{ crossValue } - } + object crossModulePartialInitError extends TestUtil.BaseModule { + object myCross extends Cross[MyCross](1, 2, 3, 4) + trait MyCross extends Cross.Module[Int] { + if (crossValue > 2) throw new Exception(s"MyCross Boom $crossValue") + def foo = T { crossValue } + } override lazy val millDiscover = Discover[this.type] } From 8f111a454425310efba8df69f1bf03133a16d7fe Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 15:06:04 +0800 Subject: [PATCH 37/57] wip getting rid of rev List[Segment] --- main/src/mill/main/ResolveCore.scala | 36 +++++++++++------------- main/src/mill/main/ResolveNonEmpty.scala | 10 ++----- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 077a1756909..9ebb49907a7 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -46,20 +46,17 @@ object ResolveCore { ) extends Failed case class Error(msg: String) extends Failed - def resolve( - remainingQuery: List[Segment], - current: Resolved, - discover: Discover[_], - args: Seq[String], - revQuerySoFar0: List[Segment] + def resolve(remainingQuery: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String], + querySoFar: Segments ): Result = remainingQuery match { case Nil => Success(Set(current)) case head :: tail => - val revQuerySoFar = head :: revQuerySoFar0 - val querySegments = Segments(revQuerySoFar0.reverse) def recurse(searchModules: Set[Resolved]): Result = { val (failures, successesLists) = searchModules - .map(resolve(tail, _, discover, args, revQuerySoFar)) + .map(r => resolve(tail, r, discover, args, querySoFar ++ Seq(head))) .partitionMap { case s: Success => Right(s.value); case f: Failed => Left(f) } val (errors, notFounds) = failures.partitionMap { @@ -71,7 +68,7 @@ object ResolveCore { else if (successesLists.flatten.nonEmpty) Success(successesLists.flatten) else notFounds.size match { case 1 => notFounds.head - case _ => notFoundResult(revQuerySoFar0, current, head, discover, args) + case _ => notFoundResult(querySoFar, current, head, discover, args) } } @@ -80,7 +77,7 @@ object ResolveCore { val resOrErr = m.valueOrErr.flatMap { obj => singleLabel match { case "__" => - catchReflectException( + val res = catchReflectException( obj .millInternal .modules @@ -90,10 +87,12 @@ object ResolveCore { resolveDirectChildren(m, None, discover, args, m.millModuleSegments) ) ) + + res case "_" => - Right(resolveDirectChildren(obj, None, discover, args, querySegments)) + Right(resolveDirectChildren(obj, None, discover, args, current.segments)) case _ => - Right(resolveDirectChildren(obj, Some(singleLabel), discover, args, querySegments)) + Right(resolveDirectChildren(obj, Some(singleLabel), discover, args, current.segments)) } } @@ -115,7 +114,7 @@ object ResolveCore { recurse(searchModules.map(m => Resolved.Module(m.millModuleSegments, Right(m))).toSet) - case _ => notFoundResult(revQuerySoFar0, current, head, discover, args) + case _ => notFoundResult(querySoFar, current, head, discover, args) } } @@ -142,7 +141,7 @@ object ResolveCore { .reflectNestedObjects0[Module](namePred) .map { case (name, f) => Resolved.Module( - segments ++ Seq(Segment.Label(name)), + segments ++ Seq(Segment.Label(decode(name))), catchReflectException(f()) ) } @@ -186,17 +185,16 @@ object ResolveCore { } def notFoundResult( - revSelectorsSoFar0: List[Segment], + querySoFar: Segments, current: Resolved, next: Segment, discover: Discover[_], args: Seq[String] ) = { - val segments = Segments(revSelectorsSoFar0.reverse) val possibleNextsOrErr = current match { case m: Resolved.Module => m.valueOrErr.map(obj => - resolveDirectChildren(obj, None, discover, args, segments) + resolveDirectChildren(obj, None, discover, args, querySoFar) .map(_.segments.value.last) ) @@ -204,7 +202,7 @@ object ResolveCore { } possibleNextsOrErr match { - case Right(nexts) => NotFound(segments, Set(current), next, nexts) + case Right(nexts) => NotFound(querySoFar, Set(current), next, nexts) case Left(err) => Error(err) } } diff --git a/main/src/mill/main/ResolveNonEmpty.scala b/main/src/mill/main/ResolveNonEmpty.scala index 6fd81e01746..feaef73b0c2 100644 --- a/main/src/mill/main/ResolveNonEmpty.scala +++ b/main/src/mill/main/ResolveNonEmpty.scala @@ -13,13 +13,9 @@ object ResolveNonEmpty { discover: Discover[_], args: Seq[String] ): Either[String, Set[Resolved]] = { - ResolveCore.resolve( - selector, - ResolveCore.Resolved.Module(current.millModuleSegments, Right(current)), - discover, - args, - Nil - ) match { + val rootResolved = ResolveCore.Resolved.Module(current.millModuleSegments, Right(current)) + + ResolveCore.resolve(selector, rootResolved, discover, args, Segments()) match { case ResolveCore.Success(value) => Right(value) case ResolveCore.NotFound(segments, found, next, possibleNexts) => val errorMsg = if (found.head.isInstanceOf[Resolved.Module]) { From db5128661d71157f6c3db1f1cada3ddd34f919c0 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 15:32:45 +0800 Subject: [PATCH 38/57] fix-tests --- main/src/mill/main/MainModule.scala | 5 +-- main/src/mill/main/Resolve.scala | 40 ++++++++++---------- main/test/src/mill/main/ResolversTests.scala | 7 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 3c56c8e013f..61905732ad6 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -339,10 +339,9 @@ trait MainModule extends mill.Module { } } - pprint.log(pathsToRemove) pathsToRemove match { -// case Left(err) => -// Result.Failure(err) + case Left(err) => + Result.Failure(err) case Right(paths) => val existing = paths.filter(p => os.exists(p)) T.log.debug(s"Cleaning ${existing.size} paths ...") diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 8f4b12c6bc1..f9a8d3196bf 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -45,25 +45,27 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { selector: Segments ) = { - val taskList: Set[Either[String, NamedTask[_]]] = resolved.collect { - case r: Resolved.Target => r.valueOrErr - case r: Resolved.Command => r.valueOrErr - case r: Resolved.Module => - r.valueOrErr match { - case Right(value: TaskModule) => - ResolveCore.resolveDirectChildren( - value, - Some(value.defaultCommandName()), - discover, - args, - value.millModuleSegments - ).head match { - case r: Resolved.Target => r.valueOrErr - case r: Resolved.Command => r.valueOrErr - } - } - - } + val taskList: Set[Either[String, NamedTask[_]]] = resolved + .collect { + case r: Resolved.Target => Some(r.valueOrErr) + case r: Resolved.Command => Some(r.valueOrErr) + case r: Resolved.Module => + r.valueOrErr match { + case Right(value: TaskModule) => + ResolveCore.resolveDirectChildren( + value, + Some(value.defaultCommandName()), + discover, + args, + value.millModuleSegments + ).head match { + case r: Resolved.Target => Some(r.valueOrErr) + case r: Resolved.Command => Some(r.valueOrErr) + } + case _ => None + } + } + .flatten if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) else Left(s"Cannot find default task to evaluate for module ${selector.render}") diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index a89ea1321a9..5e53239dfec 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -403,7 +403,6 @@ object ResolversTests extends TestSuite { "cross[211,js].suffix", "cross[212,jvm].suffix", "cross[212,js].suffix", - "cross[212,js].suffix", "cross[212,native].suffix" ) ) @@ -495,8 +494,8 @@ object ResolversTests extends TestSuite { "cross[211].cross2[js].suffix", "cross[211].cross2[native].suffix", "cross[212].cross2[jvm].suffix", - "cross[213].cross2[js].suffix", - "cross[214].cross2[native].suffix" + "cross[212].cross2[js].suffix", + "cross[212].cross2[native].suffix" ) ) } @@ -512,7 +511,7 @@ object ResolversTests extends TestSuite { "pos1Default" - check.checkSeq( Seq("cross1[210].cross2[js]"), Right(Set(_.cross1("210").cross2("js").suffixCmd())), - Set("cross1[210].cross2[js]]") + Set("cross1[210].cross2[js]") ) "pos1WithWildcard" - check.checkSeq( Seq("cross1[210].cross2[js]._"), From 6c7bc423babb3e13ef86825eb766f921877bcd67 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 17:17:07 +0800 Subject: [PATCH 39/57] fix-compile --- bsp/worker/src/mill/bsp/worker/MillBuildServer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala index feb21f05edc..9f7ca975f2d 100644 --- a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala @@ -657,7 +657,7 @@ class MillBuildServer( val mainModule = new MainModule { override implicit def millDiscover: Discover[_] = Discover[this.type] } - val compileTargetName = (module.millModuleSegments ++ Segments(Label("compile"))).render + val compileTargetName = (module.millModuleSegments ++ Seq(Label("compile"))).render log.debug(s"about to clean: ${compileTargetName}") val cleanTask = mainModule.clean(evaluator, Seq(compileTargetName): _*) val cleanResult = evaluator.evaluate( From a4dfa709aa4e806d779b26004d13e4bd56da0c23 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 17:17:22 +0800 Subject: [PATCH 40/57] cleanup --- main/core/src/mill/define/Segments.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/core/src/mill/define/Segments.scala b/main/core/src/mill/define/Segments.scala index 2580770e8cf..1291e69d02e 100644 --- a/main/core/src/mill/define/Segments.scala +++ b/main/core/src/mill/define/Segments.scala @@ -17,7 +17,7 @@ case class Segments private (value: Seq[Segment]) { case Segment.Label(head) :: rest => val stringSegments = rest.flatMap { case Segment.Label(s) => Seq(s) - case Segment.Cross(vs) => vs.map(_.toString) + case Segment.Cross(vs) => vs } head +: stringSegments case Segment.Cross(_) :: _ => From 096e6c487bb3de37ee66fdb2ab6cae4b886d2ff4 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 17:18:59 +0800 Subject: [PATCH 41/57] cleanup --- main/core/src/mill/define/Segments.scala | 1 + main/src/mill/main/ResolveCore.scala | 8 ++++---- main/src/mill/main/ResolveNonEmpty.scala | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/main/core/src/mill/define/Segments.scala b/main/core/src/mill/define/Segments.scala index 1291e69d02e..86f57692857 100644 --- a/main/core/src/mill/define/Segments.scala +++ b/main/core/src/mill/define/Segments.scala @@ -9,6 +9,7 @@ package mill.define */ case class Segments private (value: Seq[Segment]) { + def ++(other: Segment): Segments = Segments(value ++ Seq(other)) def ++(other: Seq[Segment]): Segments = Segments(value ++ other) def ++(other: Segments): Segments = Segments(value ++ other.value) diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 9ebb49907a7..3a0d114a9e7 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -141,7 +141,7 @@ object ResolveCore { .reflectNestedObjects0[Module](namePred) .map { case (name, f) => Resolved.Module( - segments ++ Seq(Segment.Label(decode(name))), + segments ++ Segment.Label(decode(name)), catchReflectException(f()) ) } @@ -150,7 +150,7 @@ object ResolveCore { case c: Cross[_] if nameOpt.isEmpty => c.segmentsToModules.map { case (k, v) => Resolved.Module( - segments ++ Seq(Segment.Cross(k)), + segments ++ Segment.Cross(k), catchReflectException(v) ) } @@ -161,7 +161,7 @@ object ResolveCore { .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) .map { m => Resolved.Target( - segments ++ Seq(Segment.Label(decode(m.getName))), + segments ++ Segment.Label(decode(m.getName)), catchReflectException(m.invoke(obj).asInstanceOf[Target[_]]) ) } @@ -171,7 +171,7 @@ object ResolveCore { .map(m => decode(m.getName)) .map { name => Resolved.Command( - segments ++ Seq(Segment.Label(name)), + segments ++ Segment.Label(name), invokeCommand( obj, name, diff --git a/main/src/mill/main/ResolveNonEmpty.scala b/main/src/mill/main/ResolveNonEmpty.scala index feaef73b0c2..e6199bfe8bd 100644 --- a/main/src/mill/main/ResolveNonEmpty.scala +++ b/main/src/mill/main/ResolveNonEmpty.scala @@ -68,7 +68,7 @@ object ResolveNonEmpty { case None => hintListLabel(prefixSegments.value) case Some(similar) => " Did you mean " + - (prefixSegments ++ Seq(Segment.Label(similar))).render + + (prefixSegments ++ Segment.Label(similar)).render + "?" } @@ -91,7 +91,7 @@ object ResolveNonEmpty { case None => hintListLabel(prefixSegments.value) case Some(similar) => " Did you mean " + - (prefixSegments ++ Seq(Segment.Cross(similar.split(',')))).render + + (prefixSegments ++ Segment.Cross(similar.split(','))).render + "?" } From a1fdd47baed8521cacfd6cbd203aaf5b70cdafdd Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 17:43:25 +0800 Subject: [PATCH 42/57] add dependency test cases, assert stack traces are shortish --- .../test/src/ModuleInitErrorTests.scala | 6 ++++ main/src/mill/main/ResolveCore.scala | 19 +++++++---- main/test/src/mill/main/ResolversTests.scala | 33 +++++++++++++++++++ main/test/src/mill/util/TestGraphs.scala | 23 +++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala b/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala index eed93e592ee..593d1816e0d 100644 --- a/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala +++ b/integration/failure/module-init-error/test/src/ModuleInitErrorTests.scala @@ -26,11 +26,15 @@ object ModuleInitErrorTests extends IntegrationTestSuite { val res = evalStdout("foo.fooTarget") assert(res.isSuccess == false) assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Foo Boom""")) + // Make sure the stack trace is "short" and does not contain all the stack + // frames from the Mill launcher + assert(fansi.Str(res.err).plainText.linesIterator.size < 20) } test("fooCommand") { val res = evalStdout("foo.fooCommand", "hello") assert(res.isSuccess == false) assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Foo Boom""")) + assert(fansi.Str(res.err).plainText.linesIterator.size < 20) } test("barTarget") { val res = evalStdout("bar.barTarget") @@ -46,11 +50,13 @@ object ModuleInitErrorTests extends IntegrationTestSuite { val res = evalStdout("bar.qux.quxTarget") assert(res.isSuccess == false) assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Qux Boom""")) + assert(fansi.Str(res.err).plainText.linesIterator.size < 20) } test("quxCommand") { val res = evalStdout("bar.qux.quxCommand", "hello") assert(res.isSuccess == false) assert(fansi.Str(res.err).plainText.contains("""java.lang.Exception: Qux Boom""")) + assert(fansi.Str(res.err).plainText.linesIterator.size < 20) } } } diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 3a0d114a9e7..cdff1ff491f 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -172,12 +172,14 @@ object ResolveCore { .map { name => Resolved.Command( segments ++ Segment.Label(name), - invokeCommand( - obj, - name, - discover.asInstanceOf[Discover[Module]], - args - ).head + catchReflectException( + invokeCommand( + obj, + name, + discover.asInstanceOf[Discover[Module]], + args + ).head + ).flatten ) } @@ -232,6 +234,11 @@ object ResolveCore { ) } match { case mainargs.Result.Success(v: Command[_]) => Right(v) + + case mainargs.Result.Failure.Exception(e) => + val outerStack = new mill.api.Result.OuterStack(new Exception().getStackTrace) + Left(mill.api.Result.Exception(e, outerStack).toString) + case f: mainargs.Result.Failure => Left( mainargs.Renderer.renderResult( diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index 5e53239dfec..edaa6501938 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -582,6 +582,39 @@ object ResolversTests extends TestSuite { res => res.isLeft && res.left.exists(_.contains("Qux Boom")) ) } + + "dependency" - { + val check = new Checker(moduleDependencyInitError) + def isShortFooTrace(res: Either[String, List[NamedTask[_]]]) = { + pprint.log(res) + res.isLeft && + res.left.exists(_.contains("Foo Boom") && + // Make sure the stack traces are truncated and short-ish, and do not + // contain the entire Mill internal call stack at point of failure + res.left.exists(_.linesIterator.size < 20)) + } + "fooTarget" - check.checkSeq0( + Seq("foo.fooTarget"), + isShortFooTrace + ) + "fooCommand" - check.checkSeq0( + Seq("foo.fooCommand", "hello"), + isShortFooTrace + ) + // Even though the `bar` module doesn't throw, `barTarget` and + // `barCommand` depend on the `fooTarget` and `fooCommand` tasks on the + // `foo` module, and the `foo` module blows up. This should turn up as + // a stack trace when we try to resolve bar + "barTarget" - check.checkSeq0( + Seq("bar.barTarget"), + isShortFooTrace + ) + "barCommand" - check.checkSeq0( + Seq("bar.barCommand", "hello"), + isShortFooTrace + ) + } + "cross" - { "simple" - { val check = new Checker(crossModuleSimpleInitError) diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 51574134f3d..0428df06bd6 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -1,5 +1,6 @@ package mill.util import TestUtil.test +import mill.api.SystemStreams import mill.define.{Command, Cross, Discover, TaskModule} import mill.{Module, T} @@ -153,6 +154,28 @@ class TestGraphs() { override lazy val millDiscover = Discover[this.type] } + object moduleDependencyInitError extends TestUtil.BaseModule { + + object foo extends Module { + def fooTarget = T { println(s"Running fooTarget"); 123 } + def fooCommand(s: String) = T.command { println(s"Running fooCommand $s") } + throw new Exception("Foo Boom") + } + + object bar extends Module { + def barTarget = T { + println(s"Running barTarget") + foo.fooTarget() + " barTarget Result" + } + def barCommand(s: String) = T.command { + foo.fooCommand(s)() + println(s"Running barCommand $s") + } + } + + override lazy val millDiscover = Discover[this.type] + } + object crossModuleSimpleInitError extends TestUtil.BaseModule { object myCross extends Cross[MyCross](1, 2, 3, 4) { throw new Exception(s"MyCross Boom") From 17fd69b4c5c66dafbbacb302b8b8bbd5e9d7a401 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 18:45:37 +0800 Subject: [PATCH 43/57] ensure output of resolution gets sorted --- main/core/src/mill/define/Cross.scala | 45 +++++++++++--------- main/src/mill/main/Resolve.scala | 17 ++++---- main/src/mill/main/ResolveCore.scala | 32 ++++++++------ main/test/src/mill/main/ResolversTests.scala | 5 +-- 4 files changed, 57 insertions(+), 42 deletions(-) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index afe6a5cbccc..c3cd84f4e3a 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -254,6 +254,7 @@ object Cross { trait Resolver[-T <: Cross.Module[_]] { def resolve[V <: T](c: Cross[V]): V } + } /** @@ -269,18 +270,24 @@ object Cross { class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mill.define.Ctx) extends mill.define.Module()(ctx) { - private val items: List[(List[Any], List[String], M)] = for { + private class Lazy[T](t: () => T) { + lazy val value = t() + } + + private val items: List[(List[Any], List[String], Lazy[M])] = for { factory <- factories.toList (crossSegments, (crossValues, make)) <- factory.crossSegmentsList.zip(factory.crossValuesListLists.zip(factory.makeList)) } yield { val relPath = ctx.segment.pathSegments - val sub = make( - ctx - .withSegments(ctx.segments ++ Seq(ctx.segment)) - .withMillSourcePath(ctx.millSourcePath / relPath) - .withSegment(Segment.Cross(crossSegments)) - .withCrossValues(factories.flatMap(_.crossValuesRaw)) + val sub = new Lazy( + () => make( + ctx + .withSegments(ctx.segments ++ Seq(ctx.segment)) + .withMillSourcePath(ctx.millSourcePath / relPath) + .withSegment(Segment.Cross(crossSegments)) + .withCrossValues(factories.flatMap(_.crossValuesRaw)) + ) ) (crossValues.toList, crossSegments.toList, sub) @@ -293,25 +300,27 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi * A list of the cross modules, in * the order the original cross values were given in */ - def crossModules: List[M] = items.collect { case (_, _, v) => v } + lazy val crossModules: List[M] = items.collect { case (_, _, v) => v.value } /** * A mapping of the raw cross values to the cross modules, in * the order the original cross values were given in */ - val valuesToModules: collection.Map[List[Any], M] = - items.map { case (values, segments, subs) => (values, subs) }.to( - collection.mutable.LinkedHashMap - ) + val valuesToModules: collection.MapView[List[Any], M] = items + .map { case (values, segments, subs) => (values, subs) } + .to(collection.mutable.LinkedHashMap) + .view + .mapValues(_.value) /** * A mapping of the string-ified string segments to the cross modules, in * the order the original cross values were given in */ - val segmentsToModules: collection.Map[List[String], M] = - items.map { case (values, segments, subs) => (segments, subs) }.to( - collection.mutable.LinkedHashMap - ) + lazy val segmentsToModules: collection.MapView[List[String], M] = items + .map { case (values, segments, subs) => (segments, subs) } + .to(collection.mutable.LinkedHashMap) + .view + .mapValues(_.value) /** * Fetch the cross module corresponding to the given cross values @@ -321,9 +330,7 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi /** * Fetch the cross module corresponding to the given cross values */ - def apply(arg0: Any, args: Any*): M = { - valuesToModules(arg0 :: args.toList) - } + def apply(arg0: Any, args: Any*): M = valuesToModules(arg0 :: args.toList) /** * Fetch the relevant cross module given the implicit resolver you have in diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index f9a8d3196bf..11490477cf5 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -17,7 +17,7 @@ import mill.util.EitherOps object ResolveSegments extends Resolve[Segments] { def handleResolved( - resolved: Set[Resolved], + resolved: Seq[Resolved], discover: Discover[_], args: Seq[String], selector: Segments @@ -28,7 +28,7 @@ object ResolveSegments extends Resolve[Segments] { object ResolveMetadata extends Resolve[String] { def handleResolved( - resolved: Set[Resolved], + resolved: Seq[Resolved], discover: Discover[_], args: Seq[String], selector: Segments @@ -39,13 +39,13 @@ object ResolveMetadata extends Resolve[String] { object ResolveTasks extends Resolve[NamedTask[Any]] { def handleResolved( - resolved: Set[Resolved], + resolved: Seq[Resolved], discover: Discover[_], args: Seq[String], selector: Segments ) = { - val taskList: Set[Either[String, NamedTask[_]]] = resolved + val taskList: Seq[Either[String, NamedTask[_]]] = resolved .collect { case r: Resolved.Target => Some(r.valueOrErr) case r: Resolved.Command => Some(r.valueOrErr) @@ -67,18 +67,18 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { } .flatten - if (taskList.nonEmpty) EitherOps.sequence(taskList).map(_.toSet[NamedTask[Any]]) + if (taskList.nonEmpty) EitherOps.sequence(taskList) else Left(s"Cannot find default task to evaluate for module ${selector.render}") } } trait Resolve[T] { def handleResolved( - resolved: Set[Resolved], + resolved: Seq[Resolved], discover: Discover[_], args: Seq[String], segments: Segments - ): Either[String, Set[T]] + ): Either[String, Seq[T]] def resolve( evaluator: Evaluator, @@ -128,8 +128,9 @@ trait Resolve[T] { args: Seq[String], sel: Segments, rootModule: BaseModule - ): Either[String, Set[T]] = { + ): Either[String, Seq[T]] = { ResolveNonEmpty.resolveNonEmpty(sel.value.toList, rootModule, rootModule.millDiscover, args) + .map(_.toSeq.sortBy(_.segments.render)) .flatMap(handleResolved(_, rootModule.millDiscover, args, sel)) } diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index cdff1ff491f..6e9848d5829 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -102,7 +102,7 @@ object ResolveCore { } case (Segment.Cross(cross), Resolved.Module(_, Right(c: Cross[_]))) => - val searchModules: Seq[Module] = + val searchModulesOrErr = catchNormalException( if (cross == Seq("__")) for ((_, v) <- c.valuesToModules.toSeq) yield v else if (cross.contains("_")) { for { @@ -112,7 +112,13 @@ object ResolveCore { } yield v } else c.segmentsToModules.get(cross.toList).toSeq - recurse(searchModules.map(m => Resolved.Module(m.millModuleSegments, Right(m))).toSet) + ) + + searchModulesOrErr match{ + case Left(err) => Error(err) + case Right(searchModules) => + recurse(searchModules.map(m => Resolved.Module(m.millModuleSegments, Right(m))).toSet) + } case _ => notFoundResult(querySoFar, current, head, discover, args) } @@ -120,11 +126,17 @@ object ResolveCore { def catchReflectException[T](t: => T): Either[String, T] = { try Right(t) - catch { - case e: java.lang.reflect.InvocationTargetException => - val outerStack = new mill.api.Result.OuterStack(new Exception().getStackTrace) - Left(mill.api.Result.Exception(e.getCause, outerStack).toString) - } + catch {case e: Exception => makeResultException(e.getCause, new Exception())} + } + + def catchNormalException[T](t: => T): Either[String, T] = { + try Right(t) + catch {case e: Exception => makeResultException(e, new Exception())} + } + + def makeResultException(e: Throwable, base: Exception) = { + val outerStack = new mill.api.Result.OuterStack(base.getStackTrace) + Left(mill.api.Result.Exception(e, outerStack).toString) } def resolveDirectChildren( @@ -234,11 +246,7 @@ object ResolveCore { ) } match { case mainargs.Result.Success(v: Command[_]) => Right(v) - - case mainargs.Result.Failure.Exception(e) => - val outerStack = new mill.api.Result.OuterStack(new Exception().getStackTrace) - Left(mill.api.Result.Exception(e, outerStack).toString) - + case mainargs.Result.Failure.Exception(e) => makeResultException(e, new Exception()) case f: mainargs.Result.Failure => Left( mainargs.Renderer.renderResult( diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolversTests.scala index edaa6501938..5b11625c9f7 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolversTests.scala @@ -586,7 +586,6 @@ object ResolversTests extends TestSuite { "dependency" - { val check = new Checker(moduleDependencyInitError) def isShortFooTrace(res: Either[String, List[NamedTask[_]]]) = { - pprint.log(res) res.isLeft && res.left.exists(_.contains("Foo Boom") && // Make sure the stack traces are truncated and short-ish, and do not @@ -629,9 +628,9 @@ object ResolversTests extends TestSuite { // If any one of the cross modules fails to initialize, even if it's // not the one you are asking for, we fail and make sure to catch and // handle the error - test - check.checkSeq0( + test - check.checkSeq( Seq("myCross[1].foo"), - res => res.isLeft && res.left.exists(_.contains("MyCross Boom 3")) + Right(Set(_.myCross(1).foo)) ) test - check.checkSeq0( Seq("myCross[3].foo"), From 2e8bb122d8a42fe8a2c19eaa0d31eced3192259e Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 18:53:26 +0800 Subject: [PATCH 44/57] . --- bsp/worker/src/mill/bsp/worker/MillBuildServer.scala | 4 ++-- main/core/src/mill/define/Cross.scala | 3 ++- main/test/src/mill/main/MainModuleTests.scala | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala index 9f7ca975f2d..7a948a382bd 100644 --- a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala @@ -657,7 +657,7 @@ class MillBuildServer( val mainModule = new MainModule { override implicit def millDiscover: Discover[_] = Discover[this.type] } - val compileTargetName = (module.millModuleSegments ++ Seq(Label("compile"))).render + val compileTargetName = (module.millModuleSegments ++ Label("compile")).render log.debug(s"about to clean: ${compileTargetName}") val cleanTask = mainModule.clean(evaluator, Seq(compileTargetName): _*) val cleanResult = evaluator.evaluate( @@ -674,7 +674,7 @@ class MillBuildServer( ) else { val outPaths = evaluator.pathsResolver.resolveDest( - module.millModuleSegments ++ Seq(Label("compile")) + module.millModuleSegments ++ Label("compile") ) val outPathSeq = Seq(outPaths.dest, outPaths.meta, outPaths.log) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index c3cd84f4e3a..3544fd609cf 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -1,6 +1,7 @@ package mill.define import language.experimental.macros +import scala.collection.SeqView import scala.reflect.macros.blackbox object Cross { @@ -300,7 +301,7 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi * A list of the cross modules, in * the order the original cross values were given in */ - lazy val crossModules: List[M] = items.collect { case (_, _, v) => v.value } + val crossModules: SeqView[M] = items.view.map { case (_, _, v) => v.value } /** * A mapping of the raw cross values to the cross modules, in diff --git a/main/test/src/mill/main/MainModuleTests.scala b/main/test/src/mill/main/MainModuleTests.scala index 4724d20e788..aca9b114add 100644 --- a/main/test/src/mill/main/MainModuleTests.scala +++ b/main/test/src/mill/main/MainModuleTests.scala @@ -165,7 +165,6 @@ object MainModuleTests extends TestSuite { os.sub / "bar" / "target.dest" / "dummy.txt" ) - println("\n\nCleaning foo.target\n\n") val r2 = ev.evaluator.evaluate(Agg(cleanModule.clean(ev.evaluator, "foo.target"))) assert(r2.failing.keyCount == 0) checkExists(false)( From c3b100d32977fa6866bbce56f112933e380c3d57 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 19:16:25 +0800 Subject: [PATCH 45/57] fox --- .../src/mill/main/{ResolversTests.scala => ResolveTests.scala} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename main/test/src/mill/main/{ResolversTests.scala => ResolveTests.scala} (99%) diff --git a/main/test/src/mill/main/ResolversTests.scala b/main/test/src/mill/main/ResolveTests.scala similarity index 99% rename from main/test/src/mill/main/ResolversTests.scala rename to main/test/src/mill/main/ResolveTests.scala index 5b11625c9f7..4fb8bd7f43c 100644 --- a/main/test/src/mill/main/ResolversTests.scala +++ b/main/test/src/mill/main/ResolveTests.scala @@ -3,7 +3,7 @@ package mill.main import mill.define.{NamedTask, SelectMode} import mill.util.TestGraphs._ import utest._ -object ResolversTests extends TestSuite { +object ResolveTests extends TestSuite { class Checker[T <: mill.define.BaseModule](module: T) { From 5071c2cea019c9bc4b3c598908ab8d983ca3f6f9 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 19:24:57 +0800 Subject: [PATCH 46/57] cleanup --- main/core/src/mill/define/Cross.scala | 4 +- main/core/src/mill/define/Discover.scala | 2 - main/core/src/mill/define/Module.scala | 73 +++++++++++++----------- main/src/mill/main/ResolveCore.scala | 2 + 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index 3544fd609cf..eabaff1e346 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -255,7 +255,6 @@ object Cross { trait Resolver[-T <: Cross.Module[_]] { def resolve[V <: T](c: Cross[V]): V } - } /** @@ -271,6 +270,9 @@ object Cross { class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mill.define.Ctx) extends mill.define.Module()(ctx) { + // We lazily initialize the instances of `Cross.Module` only when they are + // requested, to avoid unexpected failures in one module initialization + // causing problems using others. private class Lazy[T](t: () => T) { lazy val value = t() } diff --git a/main/core/src/mill/define/Discover.scala b/main/core/src/mill/define/Discover.scala index 800d0a40215..7e577e4d126 100644 --- a/main/core/src/mill/define/Discover.scala +++ b/main/core/src/mill/define/Discover.scala @@ -1,7 +1,5 @@ package mill.define -import mill.api.internal - import language.experimental.macros import scala.collection.mutable import scala.reflect.macros.blackbox diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index f425d57aa46..6e5c01915a0 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -1,7 +1,8 @@ package mill.define -import java.lang.reflect.Modifier +import mill.api.internal +import java.lang.reflect.Modifier import mill.define.ParseArgs import scala.language.experimental.macros @@ -45,40 +46,44 @@ class Module(implicit outerCtx0: mill.define.Ctx) extends mill.moduledefs.Cacher } object Module { - def reflect( - outer: Class[_], - inner: Class[_], - filter: String => Boolean, - noParams: Boolean - ): Seq[java.lang.reflect.Method] = { - for { - m <- outer.getMethods.sortBy(_.getName) - n = decode(m.getName) - if filter(n) && - ParseArgs.isLegalIdentifier(n) && - (!noParams || m.getParameterCount == 0) && - (m.getModifiers & Modifier.STATIC) == 0 && - (m.getModifiers & Modifier.ABSTRACT) == 0 && - inner.isAssignableFrom(m.getReturnType) - } yield m - } + @internal + object Internal { + def reflect( + outer: Class[_], + inner: Class[_], + filter: String => Boolean, + noParams: Boolean + ): Seq[java.lang.reflect.Method] = { + for { + m <- outer.getMethods.sortBy(_.getName) + n = decode(m.getName) + if filter(n) && + ParseArgs.isLegalIdentifier(n) && + (!noParams || m.getParameterCount == 0) && + (m.getModifiers & Modifier.STATIC) == 0 && + (m.getModifiers & Modifier.ABSTRACT) == 0 && + inner.isAssignableFrom(m.getReturnType) + } yield m + } - // For some reason, this fails to pick up concrete `object`s nested directly within - // another top-level concrete `object`. This is fine for now, since Mill's Ammonite - // script/REPL runner always wraps user code in a wrapper object/trait - def reflectNestedObjects[T: ClassTag]( - outer: Class[_], - filter: String => Boolean = Function.const(true) - ): Seq[java.lang.reflect.Member] = { - reflect(outer, classOf[Object], filter, noParams = true) ++ - outer - .getClasses - .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) - .flatMap(c => - c.getFields.find(_.getName == "MODULE$") - ).distinct + // For some reason, this fails to pick up concrete `object`s nested directly within + // another top-level concrete `object`. This is fine for now, since Mill's Ammonite + // script/REPL runner always wraps user code in a wrapper object/trait + def reflectNestedObjects[T: ClassTag]( + outer: Class[_], + filter: String => Boolean = Function.const(true) + ): Seq[java.lang.reflect.Member] = { + reflect(outer, classOf[Object], filter, noParams = true) ++ + outer + .getClasses + .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) + .flatMap(c => + c.getFields.find(_.getName == "MODULE$") + ).distinct + } } + @internal class Internal(outer: Module) { def traverse[T](f: Module => Seq[T]): Seq[T] = { def rec(m: Module): Seq[T] = f(m) ++ m.millModuleDirectChildren.flatMap(rec) @@ -102,7 +107,7 @@ object Module { lazy val millModuleLine: Int = outer.millOuterCtx.lineNum def reflect[T: ClassTag](filter: String => Boolean): Seq[T] = { - Module.reflect(outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter, noParams = true) + Module.Internal.reflect(outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter, noParams = true) .map(_.invoke(outer).asInstanceOf[T]) } @@ -120,7 +125,7 @@ object Module { def reflectNestedObjects0[T: ClassTag](filter: String => Boolean = Function.const(true)) : Seq[(String, () => T)] = { - val first = Module.reflect( + val first = Module.Internal.reflect( outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter, diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 6e9848d5829..863d267f199 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -170,6 +170,7 @@ object ResolveCore { } val targets = Module + .Internal .reflect(obj.getClass, classOf[Target[_]], namePred, noParams = true) .map { m => Resolved.Target( @@ -179,6 +180,7 @@ object ResolveCore { } val commands = Module + .Internal .reflect(obj.getClass, classOf[Command[_]], namePred, noParams = false) .map(m => decode(m.getName)) .map { name => From 540ddf738b60360594a459bd96860541c3a16497 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 19:30:40 +0800 Subject: [PATCH 47/57] . --- main/core/src/mill/define/Cross.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index eabaff1e346..4faf7623d5c 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -319,7 +319,7 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi * A mapping of the string-ified string segments to the cross modules, in * the order the original cross values were given in */ - lazy val segmentsToModules: collection.MapView[List[String], M] = items + val segmentsToModules: collection.MapView[List[String], M] = items .map { case (values, segments, subs) => (segments, subs) } .to(collection.mutable.LinkedHashMap) .view From 12c844fd3c24de9b67d210e94639398bd313ff49 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 19:31:01 +0800 Subject: [PATCH 48/57] . --- main/core/src/mill/define/Cross.scala | 4 ++-- main/core/src/mill/define/Module.scala | 23 +++++++++++-------- main/src/mill/main/ResolveCore.scala | 26 +++++++++++++--------- main/test/src/mill/main/ResolveTests.scala | 6 ++--- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index 4faf7623d5c..097544c33bd 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -283,8 +283,8 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi factory.crossSegmentsList.zip(factory.crossValuesListLists.zip(factory.makeList)) } yield { val relPath = ctx.segment.pathSegments - val sub = new Lazy( - () => make( + val sub = new Lazy(() => + make( ctx .withSegments(ctx.segments ++ Seq(ctx.segment)) .withMillSourcePath(ctx.millSourcePath / relPath) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 6e5c01915a0..fcd167c0e61 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -49,11 +49,11 @@ object Module { @internal object Internal { def reflect( - outer: Class[_], - inner: Class[_], - filter: String => Boolean, - noParams: Boolean - ): Seq[java.lang.reflect.Method] = { + outer: Class[_], + inner: Class[_], + filter: String => Boolean, + noParams: Boolean + ): Seq[java.lang.reflect.Method] = { for { m <- outer.getMethods.sortBy(_.getName) n = decode(m.getName) @@ -70,9 +70,9 @@ object Module { // another top-level concrete `object`. This is fine for now, since Mill's Ammonite // script/REPL runner always wraps user code in a wrapper object/trait def reflectNestedObjects[T: ClassTag]( - outer: Class[_], - filter: String => Boolean = Function.const(true) - ): Seq[java.lang.reflect.Member] = { + outer: Class[_], + filter: String => Boolean = Function.const(true) + ): Seq[java.lang.reflect.Member] = { reflect(outer, classOf[Object], filter, noParams = true) ++ outer .getClasses @@ -107,7 +107,12 @@ object Module { lazy val millModuleLine: Int = outer.millOuterCtx.lineNum def reflect[T: ClassTag](filter: String => Boolean): Seq[T] = { - Module.Internal.reflect(outer.getClass, implicitly[ClassTag[T]].runtimeClass, filter, noParams = true) + Module.Internal.reflect( + outer.getClass, + implicitly[ClassTag[T]].runtimeClass, + filter, + noParams = true + ) .map(_.invoke(outer).asInstanceOf[T]) } diff --git a/main/src/mill/main/ResolveCore.scala b/main/src/mill/main/ResolveCore.scala index 863d267f199..8807d3acf51 100644 --- a/main/src/mill/main/ResolveCore.scala +++ b/main/src/mill/main/ResolveCore.scala @@ -46,11 +46,12 @@ object ResolveCore { ) extends Failed case class Error(msg: String) extends Failed - def resolve(remainingQuery: List[Segment], - current: Resolved, - discover: Discover[_], - args: Seq[String], - querySoFar: Segments + def resolve( + remainingQuery: List[Segment], + current: Resolved, + discover: Discover[_], + args: Seq[String], + querySoFar: Segments ): Result = remainingQuery match { case Nil => Success(Set(current)) case head :: tail => @@ -92,7 +93,13 @@ object ResolveCore { case "_" => Right(resolveDirectChildren(obj, None, discover, args, current.segments)) case _ => - Right(resolveDirectChildren(obj, Some(singleLabel), discover, args, current.segments)) + Right(resolveDirectChildren( + obj, + Some(singleLabel), + discover, + args, + current.segments + )) } } @@ -111,10 +118,9 @@ object ResolveCore { if segments.zip(cross).forall { case (l, r) => l == r || r == "_" } } yield v } else c.segmentsToModules.get(cross.toList).toSeq - ) - searchModulesOrErr match{ + searchModulesOrErr match { case Left(err) => Error(err) case Right(searchModules) => recurse(searchModules.map(m => Resolved.Module(m.millModuleSegments, Right(m))).toSet) @@ -126,12 +132,12 @@ object ResolveCore { def catchReflectException[T](t: => T): Either[String, T] = { try Right(t) - catch {case e: Exception => makeResultException(e.getCause, new Exception())} + catch { case e: Exception => makeResultException(e.getCause, new Exception()) } } def catchNormalException[T](t: => T): Either[String, T] = { try Right(t) - catch {case e: Exception => makeResultException(e, new Exception())} + catch { case e: Exception => makeResultException(e, new Exception()) } } def makeResultException(e: Throwable, base: Exception) = { diff --git a/main/test/src/mill/main/ResolveTests.scala b/main/test/src/mill/main/ResolveTests.scala index 4fb8bd7f43c..f70b8e1e94e 100644 --- a/main/test/src/mill/main/ResolveTests.scala +++ b/main/test/src/mill/main/ResolveTests.scala @@ -588,9 +588,9 @@ object ResolveTests extends TestSuite { def isShortFooTrace(res: Either[String, List[NamedTask[_]]]) = { res.isLeft && res.left.exists(_.contains("Foo Boom") && - // Make sure the stack traces are truncated and short-ish, and do not - // contain the entire Mill internal call stack at point of failure - res.left.exists(_.linesIterator.size < 20)) + // Make sure the stack traces are truncated and short-ish, and do not + // contain the entire Mill internal call stack at point of failure + res.left.exists(_.linesIterator.size < 20)) } "fooTarget" - check.checkSeq0( Seq("foo.fooTarget"), From 78f62b5078fd8002caeaf0ba4504b7d0dd0bbd36 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 19:34:12 +0800 Subject: [PATCH 49/57] cleanup --- main/core/src/mill/define/Segment.scala | 10 ++-------- main/core/src/mill/define/Segments.scala | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/main/core/src/mill/define/Segment.scala b/main/core/src/mill/define/Segment.scala index d9e33c13db2..df3f4c088ae 100644 --- a/main/core/src/mill/define/Segment.scala +++ b/main/core/src/mill/define/Segment.scala @@ -5,15 +5,9 @@ sealed trait Segment { case Segment.Label(s) => Seq(s) case Segment.Cross(vs) => vs } - - def render: String } object Segment { - final case class Label(value: String) extends Segment { - def render = s".$value" - } - final case class Cross(value: Seq[String]) extends Segment { - def render = "[" + value.mkString(",") + "]" - } + final case class Label(value: String) extends Segment + final case class Cross(value: Seq[String]) extends Segment } diff --git a/main/core/src/mill/define/Segments.scala b/main/core/src/mill/define/Segments.scala index 86f57692857..03baeb4cb51 100644 --- a/main/core/src/mill/define/Segments.scala +++ b/main/core/src/mill/define/Segments.scala @@ -34,7 +34,10 @@ case class Segments private (value: Seq[Segment]) { def render: String = value.toList match { case Nil => "" case Segment.Label(head) :: rest => - val stringSegments = rest.map(_.render) + val stringSegments = rest.map { + case Segment.Label(s) => "." + s + case Segment.Cross(vs) => "[" + vs.mkString(",") + "]" + } head + stringSegments.mkString case Segment.Cross(_) :: _ => throw new IllegalArgumentException("Segments must start with a Label, but found a Cross.") From 53b19147b6dc27e8310f4ab68d75ac7daf8ce3cb Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 20:02:17 +0800 Subject: [PATCH 50/57] . --- main/core/src/mill/define/Cross.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index 097544c33bd..9369edefa5e 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -303,7 +303,7 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi * A list of the cross modules, in * the order the original cross values were given in */ - val crossModules: SeqView[M] = items.view.map { case (_, _, v) => v.value } + val crossModules: Seq[M] = items.map { case (_, _, v) => v.value } /** * A mapping of the raw cross values to the cross modules, in From 09e7484fbbb5d631e3cd919ba3fce112bfbdd639 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 21:52:33 +0800 Subject: [PATCH 51/57] try to fix getSimpleName on JDK8 --- main/core/src/mill/define/Cross.scala | 2 +- main/core/src/mill/define/Module.scala | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main/core/src/mill/define/Cross.scala b/main/core/src/mill/define/Cross.scala index 9369edefa5e..80c406907a2 100644 --- a/main/core/src/mill/define/Cross.scala +++ b/main/core/src/mill/define/Cross.scala @@ -303,7 +303,7 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit ctx: mi * A list of the cross modules, in * the order the original cross values were given in */ - val crossModules: Seq[M] = items.map { case (_, _, v) => v.value } + lazy val crossModules: Seq[M] = items.map { case (_, _, v) => v.value } /** * A mapping of the raw cross values to the cross modules, in diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index fcd167c0e61..1c169c5768e 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -142,11 +142,13 @@ object Module { .getClasses .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) .flatMap { c => - c.getSimpleName match { - case s"$name$$" if filter(name) => - c.getFields.find(_.getName == "MODULE$").map(f => - (name, () => f.get(c).asInstanceOf[T]) - ) + // Ideally we would just use `getSimpleName` here, but + // that blows up on JDK 8 + c.getName.stripPrefix(outer.getClass.getName) match { + case s"$$$name$$" if filter(name) => + c.getFields.find(_.getName == "MODULE$") + .map(f => (name, () => f.get(c).asInstanceOf[T])) + case _ => None } From d9ca2f8538ef1c4cc8fc1d4b9fad2e6a05900ed2 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 22:04:02 +0800 Subject: [PATCH 52/57] . --- main/core/src/mill/define/Module.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 1c169c5768e..158bc08b029 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -142,13 +142,12 @@ object Module { .getClasses .filter(implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(_)) .flatMap { c => - // Ideally we would just use `getSimpleName` here, but - // that blows up on JDK 8 c.getName.stripPrefix(outer.getClass.getName) match { - case s"$$$name$$" if filter(name) => - c.getFields.find(_.getName == "MODULE$") - .map(f => (name, () => f.get(c).asInstanceOf[T])) - + case s"$name$$" if filter(name) => + pprint.log(name) + c.getFields.find(_.getName == "MODULE$").map(f => + (name, () => f.get(c).asInstanceOf[T]) + ) case _ => None } From 18d7a6d6274bb727b777ee0cf33e8ae874861949 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 22:04:26 +0800 Subject: [PATCH 53/57] . --- main/core/src/mill/define/Module.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 158bc08b029..08d404a0536 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -145,9 +145,9 @@ object Module { c.getName.stripPrefix(outer.getClass.getName) match { case s"$name$$" if filter(name) => pprint.log(name) - c.getFields.find(_.getName == "MODULE$").map(f => - (name, () => f.get(c).asInstanceOf[T]) - ) + c.getFields.find(_.getName == "MODULE$") + .map(f => (name, () => f.get(c).asInstanceOf[T])) + case _ => None } From 6079a362b7b47ea8bcdbf862dae507f92691d8e0 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 25 Apr 2023 22:04:40 +0800 Subject: [PATCH 54/57] . --- main/core/src/mill/define/Module.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 08d404a0536..5d897e4f64f 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -130,12 +130,15 @@ object Module { def reflectNestedObjects0[T: ClassTag](filter: String => Boolean = Function.const(true)) : Seq[(String, () => T)] = { - val first = Module.Internal.reflect( - outer.getClass, - implicitly[ClassTag[T]].runtimeClass, - filter, - noParams = true - ).map(m => (m.getName, () => m.invoke(outer).asInstanceOf[T])) + val first = Module.Internal + .reflect( + outer.getClass, + implicitly[ClassTag[T]].runtimeClass, + filter, + noParams = true + ) + .map(m => (m.getName, () => m.invoke(outer).asInstanceOf[T])) + val second = outer .getClass @@ -147,7 +150,7 @@ object Module { pprint.log(name) c.getFields.find(_.getName == "MODULE$") .map(f => (name, () => f.get(c).asInstanceOf[T])) - + case _ => None } From 5f174bfc4cb8a18b632615b5da823739d1d950bf Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 26 Apr 2023 06:43:28 +0800 Subject: [PATCH 55/57] . --- main/core/src/mill/define/Module.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/main/core/src/mill/define/Module.scala b/main/core/src/mill/define/Module.scala index 5d897e4f64f..8e7e03719e5 100644 --- a/main/core/src/mill/define/Module.scala +++ b/main/core/src/mill/define/Module.scala @@ -147,7 +147,6 @@ object Module { .flatMap { c => c.getName.stripPrefix(outer.getClass.getName) match { case s"$name$$" if filter(name) => - pprint.log(name) c.getFields.find(_.getName == "MODULE$") .map(f => (name, () => f.get(c).asInstanceOf[T])) From fc6b17feceba7e2a264fc11bc63f28b8c97fb0c7 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 26 Apr 2023 20:23:30 +0800 Subject: [PATCH 56/57] tweaks --- main/core/src/mill/define/ExpandBraces.scala | 48 +++++++++----------- main/src/mill/main/Resolve.scala | 27 +++++------ 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala index 73af33e2c53..d05a3b84c17 100644 --- a/main/core/src/mill/define/ExpandBraces.scala +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -9,33 +9,30 @@ object ExpandBraces { case class Expand(values: List[List[Fragment]]) extends Fragment } + def expandRec(frags: List[Fragment]): List[List[String]] = frags match { + case Nil => List(List()) + case head :: tail => + val tailStrings = expandRec(tail) + head match { + case Fragment.Keep(s) => tailStrings.map(s :: _) + case Fragment.Expand(fragmentLists) => + if (fragmentLists.length == 1) { + for { + lhs <- fragmentLists.flatMap(expandRec) + rhs <- tailStrings + } yield List("{") ::: lhs ::: List("}") ::: rhs + } else for { + lhs <- fragmentLists.flatMap(expandRec) + rhs <- tailStrings + } yield lhs ::: rhs + } + } + def expandBraces(selectorString: String): Either[String, Seq[String]] = { parse(selectorString, parser(_)) match { case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}") case Parsed.Success(fragmentLists, _) => - def processFragmentSequence(remaining: List[Fragment]): List[List[String]] = - remaining match { - case Nil => List(List()) - case head :: tail => - val tailStrings = processFragmentSequence(tail) - head match { - case Fragment.Keep(s) => tailStrings.map(s :: _) - case Fragment.Expand(fragmentLists) => - if (fragmentLists.length == 1) { - for { - lhs <- fragmentLists.flatMap(processFragmentSequence) - rhs <- tailStrings - } yield List("{") ::: lhs ::: List("}") ::: rhs - } else for { - lhs <- fragmentLists.flatMap(processFragmentSequence) - rhs <- tailStrings - } yield lhs ::: rhs - } - } - - val res = processFragmentSequence(fragmentLists.toList).map(_.mkString) - - Right(res) + Right(expandRec(fragmentLists.toList).map(_.mkString)) } } @@ -45,9 +42,8 @@ object ExpandBraces { private def emptyExpansionBranch[_p: P] = P("").map(_ => List(Fragment.Keep(""))) private def toExpand[_p: P]: P[Fragment] = - P("{" ~ (braceParser.rep(1) | emptyExpansionBranch).rep(sep = ",") ~ "}").map(x => - Fragment.Expand(x.toList.map(_.toList)) - ) + P("{" ~ (braceParser.rep(1) | emptyExpansionBranch).rep(sep = ",") ~ "}") + .map(x => Fragment.Expand(x.toList.map(_.toList))) private def braceParser[_p: P]: P[Fragment] = P(toExpand | plainChars) diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 11490477cf5..72795b7eb79 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -46,26 +46,23 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { ) = { val taskList: Seq[Either[String, NamedTask[_]]] = resolved - .collect { + .flatMap { case r: Resolved.Target => Some(r.valueOrErr) case r: Resolved.Command => Some(r.valueOrErr) case r: Resolved.Module => - r.valueOrErr match { - case Right(value: TaskModule) => - ResolveCore.resolveDirectChildren( - value, - Some(value.defaultCommandName()), - discover, - args, - value.millModuleSegments - ).head match { - case r: Resolved.Target => Some(r.valueOrErr) - case r: Resolved.Command => Some(r.valueOrErr) - } - case _ => None + r.valueOrErr.toOption.collect{ case value: TaskModule => + ResolveCore.resolveDirectChildren( + value, + Some(value.defaultCommandName()), + discover, + args, + value.millModuleSegments + ).head match { + case r: Resolved.Target => r.valueOrErr + case r: Resolved.Command => r.valueOrErr + } } } - .flatten if (taskList.nonEmpty) EitherOps.sequence(taskList) else Left(s"Cannot find default task to evaluate for module ${selector.render}") From 6b5f726ca8d400a5a12a957b93481fcf6bb7d362 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 07:09:06 +0800 Subject: [PATCH 57/57] . --- main/core/src/mill/define/ExpandBraces.scala | 30 ++++++++++---------- main/src/mill/main/Resolve.scala | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/main/core/src/mill/define/ExpandBraces.scala b/main/core/src/mill/define/ExpandBraces.scala index d05a3b84c17..931dd729724 100644 --- a/main/core/src/mill/define/ExpandBraces.scala +++ b/main/core/src/mill/define/ExpandBraces.scala @@ -10,23 +10,23 @@ object ExpandBraces { } def expandRec(frags: List[Fragment]): List[List[String]] = frags match { - case Nil => List(List()) - case head :: tail => - val tailStrings = expandRec(tail) - head match { - case Fragment.Keep(s) => tailStrings.map(s :: _) - case Fragment.Expand(fragmentLists) => - if (fragmentLists.length == 1) { - for { - lhs <- fragmentLists.flatMap(expandRec) - rhs <- tailStrings - } yield List("{") ::: lhs ::: List("}") ::: rhs - } else for { + case Nil => List(List()) + case head :: tail => + val tailStrings = expandRec(tail) + head match { + case Fragment.Keep(s) => tailStrings.map(s :: _) + case Fragment.Expand(fragmentLists) => + if (fragmentLists.length == 1) { + for { lhs <- fragmentLists.flatMap(expandRec) rhs <- tailStrings - } yield lhs ::: rhs - } - } + } yield List("{") ::: lhs ::: List("}") ::: rhs + } else for { + lhs <- fragmentLists.flatMap(expandRec) + rhs <- tailStrings + } yield lhs ::: rhs + } + } def expandBraces(selectorString: String): Either[String, Seq[String]] = { parse(selectorString, parser(_)) match { diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index 72795b7eb79..df3b9a74541 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -50,7 +50,7 @@ object ResolveTasks extends Resolve[NamedTask[Any]] { case r: Resolved.Target => Some(r.valueOrErr) case r: Resolved.Command => Some(r.valueOrErr) case r: Resolved.Module => - r.valueOrErr.toOption.collect{ case value: TaskModule => + r.valueOrErr.toOption.collect { case value: TaskModule => ResolveCore.resolveDirectChildren( value, Some(value.defaultCommandName()),