From 478bf87d3f140eaf2671663b5b3c1758354ec856 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 14 Jan 2018 03:31:06 -0800 Subject: Everything seems to compile with new, minimal `Discover` implementation... --- core/src/main/scala/mill/define/Discover.scala | 44 ++++++++++++++++++++++ core/src/main/scala/mill/define/Module.scala | 21 +++++++++-- core/src/main/scala/mill/eval/Evaluator.scala | 10 +++-- core/src/main/scala/mill/main/MainRunner.scala | 9 +++-- .../main/scala/mill/main/ReplApplyHandler.scala | 24 ++++++++---- core/src/main/scala/mill/main/Resolve.scala | 12 +++--- core/src/main/scala/mill/main/RunScript.scala | 40 +++++++++++++------- 7 files changed, 124 insertions(+), 36 deletions(-) create mode 100644 core/src/main/scala/mill/define/Discover.scala (limited to 'core/src/main/scala') diff --git a/core/src/main/scala/mill/define/Discover.scala b/core/src/main/scala/mill/define/Discover.scala new file mode 100644 index 00000000..a5bcd8c6 --- /dev/null +++ b/core/src/main/scala/mill/define/Discover.scala @@ -0,0 +1,44 @@ +package mill.define +import language.experimental.macros +import ammonite.main.Router.EntryPoint + +import scala.collection.mutable +import scala.reflect.macros.blackbox + +case class Discover(value: Map[Class[_], Seq[EntryPoint[_]]]) +object Discover { + def apply[T]: Discover = macro applyImpl[T] + + def applyImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Discover] = { + import c.universe._ + val seen = mutable.Set.empty[Type] + def rec(tpe: Type): Unit = { + println("Rec! " + tpe) + if (!seen(tpe)){ + seen.add(tpe) + for{ + m <- tpe.members + memberTpe = m.typeSignature + if memberTpe.resultType <:< typeOf[mill.define.Module] && memberTpe.paramLists.isEmpty + } rec(memberTpe.resultType) + } + } + rec(weakTypeOf[T]) + + val router = new ammonite.main.Router(c) + val mapping = for{ + discoveredModuleType <- seen + val routes = router.getAllRoutesForClass( + discoveredModuleType.asInstanceOf[router.c.Type], + _.returnType <:< weakTypeOf[mill.define.Command[_]].asInstanceOf[router.c.Type] + ).map(_.asInstanceOf[c.Tree]) + if routes.nonEmpty + } yield { + val lhs = q"classOf[${discoveredModuleType.typeSymbol.asClass}]" + val rhs = q"scala.Seq[ammonite.main.Router.EntryPoint[${discoveredModuleType.typeSymbol.asClass}]](..$routes)" + q"$lhs -> $rhs" + } + + c.Expr[Discover](q"mill.define.Discover(scala.collection.immutable.Map(..$mapping))") + } +} diff --git a/core/src/main/scala/mill/define/Module.scala b/core/src/main/scala/mill/define/Module.scala index f2ebe1f3..c9ce523c 100644 --- a/core/src/main/scala/mill/define/Module.scala +++ b/core/src/main/scala/mill/define/Module.scala @@ -25,7 +25,6 @@ object Module{ * the concrete instance. */ class Module(implicit ctx0: mill.define.Ctx, cmds: Module.Cmds) extends mill.moduledefs.Cacher{ - def commands: Seq[EntryPoint[Module]] = cmds.value def traverse[T](f: Module => Seq[T]): Seq[T] = { def rec(m: Module): Seq[T] = { @@ -45,8 +44,8 @@ class Module(implicit ctx0: mill.define.Ctx, cmds: Module.Cmds) extends mill.mod lazy val targets = segmentsToTargets.valuesIterator.toSet lazy val segmentsToCommands = traverse{ - m => m.commands.map(e => m.ctx.segments ++ Seq(Segment.Label(e.name)) -> e) - }.toMap + m => m.reflectNames[Command[_]].map(c => m.ctx.segments ++ Seq(Segment.Label(c))) + }.toSet def ctx = ctx0 // Ensure we do not propagate the implicit parameters as implicits within @@ -71,6 +70,22 @@ class Module(implicit ctx0: mill.define.Ctx, cmds: Module.Cmds) extends mill.mod .filter(implicitly[ClassTag[T]].runtimeClass isAssignableFrom _.getReturnType) .map(_.invoke(this).asInstanceOf[T]) } + def reflectNames[T: ClassTag] = { + this + .getClass + .getMethods + .filter(x => (x.getModifiers & Modifier.STATIC) == 0) + .filter(implicitly[ClassTag[T]].runtimeClass isAssignableFrom _.getReturnType) + .map(_.getName) + } + def reflectNestedObjects[T: ClassTag] = { + reflect[T] ++ + this + .getClass + .getClasses + .filter(implicitly[ClassTag[T]].runtimeClass isAssignableFrom _) + .flatMap(c => c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T])) + } } trait TaskModule extends Module { def defaultCommandName(): String diff --git a/core/src/main/scala/mill/eval/Evaluator.scala b/core/src/main/scala/mill/eval/Evaluator.scala index ee4ec27b..fbeb0c62 100644 --- a/core/src/main/scala/mill/eval/Evaluator.scala +++ b/core/src/main/scala/mill/eval/Evaluator.scala @@ -4,8 +4,9 @@ import java.net.URLClassLoader import ammonite.ops._ import ammonite.runtime.SpecialClassLoader -import mill.define.{Segment, Segments, NamedTask, Graph, Target, Task} +import mill.define.{Graph, NamedTask, Segment, Segments, Target, Task} import mill.util +import mill.util.Ctx.Loader import mill.util._ import mill.util.Strict.Agg @@ -23,6 +24,9 @@ case class Labelled[T](target: NamedTask[T], case _ => None } } +object RootModuleLoader extends Loader[mill.Module] { + def make() = ??? +} class Evaluator[T](val workspacePath: Path, val basePath: Path, val rootModule: mill.Module, @@ -32,7 +36,7 @@ class Evaluator[T](val workspacePath: Path, val workerCache = mutable.Map.empty[Ctx.Loader[_], Any] -// workerCache(Discovered.Mapping) = rootModule + workerCache(RootModuleLoader) = rootModule def evaluate(goals: Agg[Task[_]]): Evaluator.Results = { mkdir(workspacePath) @@ -43,7 +47,7 @@ class Evaluator[T](val workspacePath: Path, val segments = t.ctx.segments val (finalTaskOverrides, enclosing) = t match{ case t: Target[_] => rootModule.segmentsToTargets(segments).ctx.overrides -> t.ctx.enclosing - case c: mill.define.Command[_] => rootModule.segmentsToCommands(segments).overrides -> c.ctx.enclosing + case c: mill.define.Command[_] => 0 -> c.ctx.enclosing } val additional = if (finalTaskOverrides == t.ctx.overrides) Nil diff --git a/core/src/main/scala/mill/main/MainRunner.scala b/core/src/main/scala/mill/main/MainRunner.scala index 49fcfe2a..fd16161f 100644 --- a/core/src/main/scala/mill/main/MainRunner.scala +++ b/core/src/main/scala/mill/main/MainRunner.scala @@ -5,6 +5,7 @@ import ammonite.Main import ammonite.interp.{Interpreter, Preprocessor} import ammonite.ops.Path import ammonite.util._ +import mill.define.Discover import mill.eval.{Evaluator, PathRef} import mill.util.PrintLogger import upickle.Js @@ -23,7 +24,7 @@ class MainRunner(config: ammonite.main.Cli.Config, config, outprintStream, errPrintStream, stdIn, outprintStream, errPrintStream ){ - var lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_])] = None + var lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_], Discover)] = None override def runScript(scriptPath: Path, scriptArgs: List[String]) = watchLoop( @@ -47,9 +48,9 @@ class MainRunner(config: ammonite.main.Cli.Config, result match{ case Res.Success(data) => - val (eval, evaluationWatches, res) = data + val (eval, discover, evaluationWatches, res) = data - lastEvaluator = Some((interpWatched, eval)) + lastEvaluator = Some((interpWatched, eval, discover)) (Res(res), interpWatched ++ evaluationWatches) case _ => (result, interpWatched) @@ -96,6 +97,8 @@ class MainRunner(config: ammonite.main.Cli.Config, | // Stub to make sure Ammonite has something to call after it evaluates a script, | // even if it does nothing... | def $$main() = Iterator[String]() + | + | val millDiscover = mill.define.Discover[$wrapName] |} | |sealed trait $wrapName extends mill.Module{ diff --git a/core/src/main/scala/mill/main/ReplApplyHandler.scala b/core/src/main/scala/mill/main/ReplApplyHandler.scala index f7f44270..909f25f7 100644 --- a/core/src/main/scala/mill/main/ReplApplyHandler.scala +++ b/core/src/main/scala/mill/main/ReplApplyHandler.scala @@ -2,6 +2,7 @@ package mill.main import mill.define.Applicative.ApplyHandler +import mill.define.Segment.Label import mill.define._ import mill.eval.{Evaluator, Result} import mill.util.Strict.Agg @@ -10,7 +11,8 @@ import scala.collection.mutable object ReplApplyHandler{ def apply[T](colors: ammonite.util.Colors, pprinter0: pprint.PPrinter, - rootModule: mill.Module) = { + rootModule: mill.Module, + discover: Discover) = { new ReplApplyHandler( pprinter0, new mill.eval.Evaluator( @@ -24,11 +26,14 @@ object ReplApplyHandler{ System.err, System.err ) - ) + ), + discover ) } } -class ReplApplyHandler(pprinter0: pprint.PPrinter, evaluator: Evaluator[_]) extends ApplyHandler[Task] { +class ReplApplyHandler(pprinter0: pprint.PPrinter, + evaluator: Evaluator[_], + discover: Discover) extends ApplyHandler[Task] { // Evaluate classLoaderSig only once in the REPL to avoid busting caches // as the user enters more REPL commands and changes the classpath val classLoaderSig = Evaluator.classLoaderSig @@ -75,11 +80,14 @@ class ReplApplyHandler(pprinter0: pprint.PPrinter, evaluator: Evaluator[_]) exte Iterator(m.millModuleEnclosing, ":", m.millModuleLine.toString) ++ (if (m.reflect[mill.Module].isEmpty) Nil else ctx.applyPrefixColor("\nChildren:").toString +: m.reflect[mill.Module].map("\n ." + _.ctx.segments.render)) ++ - (if (m.commands.isEmpty) Nil - else ctx.applyPrefixColor("\nCommands:").toString +: m.commands.sortBy(_.name).map{c => - "\n ." + c.name + "(" + - c.argSignatures.map(s => s.name + ": " + s.typeString).mkString(", ") + - ")()" + (discover.value.get(m.getClass) match{ + case None => Nil + case Some(commands) => + ctx.applyPrefixColor("\nCommands:").toString +: commands.map{c => + "\n ." + c.name + "(" + + c.argSignatures.map(s => s.name + ": " + s.typeString).mkString(", ") + + ")()" + } }) ++ (if (m.reflect[Target[_]].isEmpty) Nil else { diff --git a/core/src/main/scala/mill/main/Resolve.scala b/core/src/main/scala/mill/main/Resolve.scala index e50e0ca9..df3c250d 100644 --- a/core/src/main/scala/mill/main/Resolve.scala +++ b/core/src/main/scala/mill/main/Resolve.scala @@ -3,10 +3,12 @@ package mill.main import mill.define._ import mill.define.TaskModule import ammonite.main.Router +import ammonite.main.Router.EntryPoint object Resolve { def resolve[T, V](remainingSelector: List[Segment], obj: mill.Module, + discover: Discover, rest: Seq[String], remainingCrossSelectors: List[List[String]], revSelectorsSoFar: List[Segment]): Either[String, Task[Any]] = { @@ -21,8 +23,8 @@ object Resolve { .map(Right(_)) def invokeCommand[V](target: mill.Module, name: String) = for{ - cmd <- target.commands.find(_.name == name) - } yield cmd.invoke(target, ammonite.main.Scripts.groupArgs(rest.toList)) match { + cmd <- discover.value.get(target.getClass).toSeq.flatten.find(_.name == name) + } yield cmd.asInstanceOf[EntryPoint[mill.Module]].invoke(target, ammonite.main.Scripts.groupArgs(rest.toList)) match { case Router.Result.Success(v) => Right(v) case _ => Left(s"Command failed $last") } @@ -52,10 +54,10 @@ object Resolve { val newRevSelectorsSoFar = head :: revSelectorsSoFar head match{ case Segment.Label(singleLabel) => - obj.reflect[mill.Module].find{ + obj.reflectNestedObjects[mill.Module].find{ _.ctx.segment == Segment.Label(singleLabel) } match{ - case Some(child: mill.Module) => resolve(tail, child, rest, remainingCrossSelectors, newRevSelectorsSoFar) + case Some(child: mill.Module) => resolve(tail, child, discover, rest, remainingCrossSelectors, newRevSelectorsSoFar) case None => Left("Cannot resolve module " + Segments(newRevSelectorsSoFar.reverse:_*).render) } @@ -63,7 +65,7 @@ object Resolve { obj match{ case c: Cross[_] => c.itemMap.get(cross.toList) match{ - case Some(m: mill.Module) => resolve(tail, m, rest, remainingCrossSelectors, newRevSelectorsSoFar) + case Some(m: mill.Module) => resolve(tail, m, discover, rest, remainingCrossSelectors, newRevSelectorsSoFar) case None => Left("Cannot resolve cross " + Segments(newRevSelectorsSoFar.reverse:_*).render) } diff --git a/core/src/main/scala/mill/main/RunScript.scala b/core/src/main/scala/mill/main/RunScript.scala index acabcf0a..8ad144d3 100644 --- a/core/src/main/scala/mill/main/RunScript.scala +++ b/core/src/main/scala/mill/main/RunScript.scala @@ -7,8 +7,7 @@ import ammonite.ops.{Path, read} import ammonite.util.Util.CodeSource import ammonite.util.{Name, Res, Util} import mill.{PathRef, define} -import mill.define.Task -import mill.define.Segment +import mill.define.{Discover, Segment, Task} import mill.eval.{Evaluator, Result} import mill.util.{EitherOps, Logger} import mill.util.Strict.Agg @@ -24,15 +23,15 @@ object RunScript{ path: Path, instantiateInterpreter: => Either[(Res.Failing, Seq[(Path, Long)]), ammonite.interp.Interpreter], scriptArgs: Seq[String], - lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_])], + lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_], Discover)], log: Logger) - : (Res[(Evaluator[_], Seq[(Path, Long)], Either[String, Seq[Js.Value]])], Seq[(Path, Long)]) = { + : (Res[(Evaluator[_], Discover, Seq[(Path, Long)], Either[String, Seq[Js.Value]])], Seq[(Path, Long)]) = { val (evalRes, interpWatched) = lastEvaluator match{ - case Some((prevInterpWatchedSig, prevEvaluator)) + case Some((prevInterpWatchedSig, prevEvaluator, prevDiscover)) if watchedSigUnchanged(prevInterpWatchedSig) => - (Res.Success(prevEvaluator), prevInterpWatchedSig) + (Res.Success(prevEvaluator -> prevDiscover), prevInterpWatchedSig) case _ => instantiateInterpreter match{ @@ -40,15 +39,15 @@ object RunScript{ case Right(interp) => interp.watch(path) val eval = - for(mapping <- evaluateMapping(wd, path, interp)) - yield new Evaluator(wd / 'out, wd, mapping, log) + for((mapping, discover) <- evaluateMapping(wd, path, interp)) + yield (new Evaluator(wd / 'out, wd, mapping, log), discover) (eval, interp.watchedFiles) } } val evaluated = for{ - evaluator <- evalRes - (evalWatches, res) <- Res(evaluateTarget(evaluator, scriptArgs)) + (evaluator, discover) <- evalRes + (evalWatches, res) <- Res(evaluateTarget(evaluator, discover, scriptArgs)) } yield { val alreadyStale = evalWatches.exists(p => p.sig != new PathRef(p.path, p.quick).sig) // If the file changed between the creation of the original @@ -61,7 +60,7 @@ object RunScript{ if (alreadyStale) evalWatches.map(_.path -> util.Random.nextLong()) else evalWatches.map(p => p.path -> Interpreter.pathSignature(p.path)) - (evaluator, evaluationWatches, res.map(_.flatMap(_._2))) + (evaluator, discover, evaluationWatches, res.map(_.flatMap(_._2))) } (evaluated, interpWatched) } @@ -72,7 +71,7 @@ object RunScript{ def evaluateMapping(wd: Path, path: Path, - interp: ammonite.interp.Interpreter): Res[mill.Module] = { + interp: ammonite.interp.Interpreter): Res[(mill.Module, Discover)] = { val (pkg, wrapper) = Util.pathToPackageWrapper(Seq(), path relativeTo wd) @@ -98,7 +97,7 @@ object RunScript{ .evalClassloader .loadClass(buildClsName) - mapping <- try { + module <- try { Util.withContextClassloader(interp.evalClassloader) { Res.Success( buildCls.getField("MODULE$") @@ -109,11 +108,23 @@ object RunScript{ } catch { case e: Throwable => Res.Exception(e, "") } + discover <- try { + Util.withContextClassloader(interp.evalClassloader) { + Res.Success( + buildCls.getMethod("millDiscover") + .invoke(null) + .asInstanceOf[Discover] + ) + } + } catch { + case e: Throwable => Res.Exception(e, "") + } // _ <- Res(consistencyCheck(mapping)) - } yield mapping + } yield (module, discover) } def evaluateTarget[T](evaluator: Evaluator[_], + discover: Discover, scriptArgs: Seq[String]) = { for { parsed <- ParseArgs(scriptArgs) @@ -126,6 +137,7 @@ object RunScript{ } mill.main.Resolve.resolve( sel, evaluator.rootModule, + discover, args, crossSelectors, Nil ) } -- cgit v1.2.3