From a687f3908c84d5149fe8ef32bfb73872d65675d2 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 10 Dec 2017 15:43:43 -0800 Subject: First pass at simplifying Ammonite integration and enabling the REPL. Can query for terms like `Core.compile`, but still can't run `Core.compile()` --- core/src/main/scala/mill/Main.scala | 249 +++++--------------------- core/src/main/scala/mill/main/Resolve.scala | 78 ++++++++ core/src/test/scala/mill/main/MainTests.scala | 2 +- 3 files changed, 127 insertions(+), 202 deletions(-) create mode 100644 core/src/main/scala/mill/main/Resolve.scala diff --git a/core/src/main/scala/mill/Main.scala b/core/src/main/scala/mill/Main.scala index 24cd7ed1..711f251e 100644 --- a/core/src/main/scala/mill/Main.scala +++ b/core/src/main/scala/mill/Main.scala @@ -37,76 +37,6 @@ object Main { } } - def resolve[T, V](remainingSelector: List[Mirror.Segment], - hierarchy: Mirror[T, V], - obj: T, - rest: Seq[String], - remainingCrossSelectors: List[List[String]], - revSelectorsSoFar: List[Mirror.Segment]): Either[String, Task[Any]] = { - - remainingSelector match{ - case Mirror.Segment.Cross(_) :: Nil => Left("Selector cannot start with a [cross] segment") - case Mirror.Segment.Label(last) :: Nil => - def target = - hierarchy.targets - .find(_.label == last) - .map(x => Right(x.run(hierarchy.node(obj, remainingCrossSelectors)))) - - def invokeCommand[V](mirror: Mirror[T, V], name: String) = for{ - cmd <- mirror.commands.find(_.name == name) - } yield cmd.invoke( - mirror.node(obj, remainingCrossSelectors), - ammonite.main.Scripts.groupArgs(rest.toList) - ) match { - case Router.Result.Success(v) => Right(v) - case _ => Left(s"Command failed $last") - } - - def runDefault = for{ - (label, child) <- hierarchy.children - if label == last - res <- child.node(obj, remainingCrossSelectors) match{ - case taskMod: TaskModule => Some(invokeCommand(child, taskMod.defaultCommandName())) - case _ => None - } - } yield res - - def command = invokeCommand(hierarchy, last) - - command orElse target orElse runDefault.headOption.flatten match{ - case None => Left("Cannot resolve task " + Mirror.renderSelector( - (Mirror.Segment.Label(last) :: revSelectorsSoFar).reverse) - ) - case Some(either) => either - } - - - case head :: tail => - val newRevSelectorsSoFar = head :: revSelectorsSoFar - head match{ - case Mirror.Segment.Label(singleLabel) => - hierarchy.children.collectFirst{ - case (label, child) if label == singleLabel => child - } match{ - case Some(child) => resolve(tail, child, obj, rest, remainingCrossSelectors, newRevSelectorsSoFar) - case None => Left("Cannot resolve module " + Mirror.renderSelector(newRevSelectorsSoFar.reverse)) - } - - case Mirror.Segment.Cross(cross) => - val Some((crossGen, childMirror)) = hierarchy.crossChildren - val crossOptions = crossGen(hierarchy.node(obj, remainingCrossSelectors)) - if (crossOptions.contains(cross)){ - resolve(tail, childMirror, obj, rest, remainingCrossSelectors, newRevSelectorsSoFar) - }else{ - Left("Cannot resolve cross " + Mirror.renderSelector(newRevSelectorsSoFar.reverse)) - } - - - } - - case Nil => Left("Selector cannot be empty") - } - } def discoverMirror[T: Discovered](obj: T): Either[String, Discovered[T]] = { val discovered = implicitly[Discovered[T]] @@ -162,7 +92,7 @@ object Main { case Mirror.Segment.Cross(x) => x.toList.map(_.toString) case _ => Nil } - target <- resolve(sel, disc.mirror, obj, rest, crossSelectors, Nil) + target <- mill.main.Resolve.resolve(sel, disc.mirror, obj, rest, crossSelectors, Nil) evaluator = new Evaluator(pwd / 'out, Discovered.mapping(obj)(disc), log) _ <- evaluate(evaluator, target, watch).toLeft(()) } yield () @@ -182,141 +112,58 @@ object Main { watch: Boolean = false) def main(args: Array[String]): Unit = { - val startTime = System.currentTimeMillis() - - - import ammonite.main.Cli.Arg - val signature = Seq( - Arg[Config, Path]( - "home", Some('h'), - "The home directory of the REPL; where it looks for config and caches", - (c, v) => c.copy(home = v) - ), - Arg[Config, Unit]( - "help", None, - """Print this message""".stripMargin, - (c, v) => c.copy(help = true) - ), - Arg[Config, Boolean]( - "color", None, - """Enable or disable colored output; by default colors are enabled - |in both REPL and scripts if the console is interactive, and disabled - |otherwise""".stripMargin, - (c, v) => c.copy(colored = Some(v)) - ), - Arg[Config, Unit]( - "repl", Some('r'), - "Open a build REPL", - (c, v) => c.copy(repl = true) - ), - Arg[Config, Unit]( - "watch", Some('w'), - "Watch and re-run your build when it changes", - (c, v) => c.copy(watch = true) - ) + val syntheticPath = pwd / 'out / "run.sc" + write.over( + syntheticPath, + s"""import $$file.^.build + |import mill._ + | + |@main def run(args: String*) = mill.Main(args, build, interp.watch, true/*interp.colors()*/) + | + |@main def idea() = mill.scalaplugin.GenIdea(build)""".stripMargin ) - ammonite.main.Cli.groupArgs(args.toList, signature, Config()) match{ - case Left(err) => - case Right((config, leftover)) => - if (config.help) { - val leftMargin = signature.map(ammonite.main.Cli.showArg(_).length).max + 2 - System.err.println(ammonite.main.Cli.formatBlock(signature, leftMargin).mkString("\n")) - System.exit(0) + import ammonite.main.Cli + var repl = false + val replCliArg = Cli.Arg[Cli.Config, Unit]( + "repl", + None, + "Open a Build REPL", + (x, _) => { + repl = true + x + } + ) + Cli.groupArgs( + args.toList, + Cli.ammoniteArgSignature :+ replCliArg, + Cli.Config() + ) match{ + case Left(msg) => + System.err.println(msg) + System.exit(1) + case Right((cliConfig, leftoverArgs)) => + if (repl){ + + val runner = new ammonite.MainRunner( + cliConfig.copy( + predefFile = Some(pwd / "build.sc"), + welcomeBanner = Some("") + ), + System.out, System.err, + System.in, System.out, System.err + ) + runner.printInfo("Loading...") + runner.runRepl() } else { - val res = new Main(config).run(leftover, startTime) - System.exit(res) + val runner = new ammonite.MainRunner( + cliConfig, + System.out, System.err, + System.in, System.out, System.err + ) + runner.runScript(syntheticPath, leftoverArgs) } } } } - -class Main(config: Main.Config){ - val coloredOutput = config.colored.getOrElse(ammonite.Main.isInteractive()) - val log = new PrintLogger(coloredOutput) - - - def watchAndWait(watched: Seq[(Path, Long)]) = { - log.info(s"Watching for changes to ${watched.length} files... (Ctrl-C to exit)") - def statAll() = watched.forall{ case (file, lastMTime) => - Interpreter.pathSignature(file) == lastMTime - } - - while(statAll()) Thread.sleep(100) - } - - def handleWatchRes[T](res: Res[T], printing: Boolean) = res match { - case Res.Failure(msg) => - log.error(msg) - false - - case Res.Exception(ex, s) => - log.error( - Repl.showException(ex, fansi.Color.Red, fansi.Attr.Reset, fansi.Color.Green) - ) - false - - case Res.Success(value) => - if (printing && value != ()) println(pprint.PPrinter.BlackWhite(value)) - true - - case Res.Skip => true // do nothing on success, everything's already happened - case Res.Exit(_) => ??? - } - - def run(leftover: List[String], startTime0: Long): Int = { - - var exitCode = 0 - var startTime = startTime0 - val loop = config.watch - - do { - val watchedFiles = if (config.repl) { - val repl = ammonite.Main( - predefFile = Some(pwd / "build.sc") - ).instantiateRepl(remoteLogger = None).right.get - repl.interp.initializePredef() - repl.run() - repl.interp.watchedFiles - } else { - val interp = ammonite.Main( - predefFile = Some(pwd / "build.sc") - ).instantiateInterpreter()match{ - case Left(x) => println(x); ??? - case Right(x) => x - } - - interp.initializePredef() - val syntheticPath = pwd / 'out / "run.sc" - write.over( - syntheticPath, - s"""@main def run(args: String*) = mill.Main(args, ammonite.predef.FilePredef, interp.watch, $coloredOutput) - | - |@main def idea() = mill.scalaplugin.GenIdea(ammonite.predef.FilePredef) - """.stripMargin - ) - - val res = ammonite.main.Scripts.runScript( - pwd, - syntheticPath, - interp, - Scripts.groupArgs(leftover) - ) - res match{ - case Res.Success(v: Int) => exitCode = v - case _ => exitCode = 1 - } - - handleWatchRes(res, false) - interp.watchedFiles - } - - val delta = System.currentTimeMillis() - startTime - log.info("Finished in " + delta/1000.0 + "s") - if (loop) watchAndWait(watchedFiles) - startTime = System.currentTimeMillis() - } while(loop) - exitCode - } -} diff --git a/core/src/main/scala/mill/main/Resolve.scala b/core/src/main/scala/mill/main/Resolve.scala new file mode 100644 index 00000000..9cfc33b5 --- /dev/null +++ b/core/src/main/scala/mill/main/Resolve.scala @@ -0,0 +1,78 @@ +package mill.main + +import mill.define.Task +import mill.define.Task.TaskModule +import mill.discover.{Mirror, Router} + +object Resolve { + def resolve[T, V](remainingSelector: List[Mirror.Segment], + hierarchy: Mirror[T, V], + obj: T, + rest: Seq[String], + remainingCrossSelectors: List[List[String]], + revSelectorsSoFar: List[Mirror.Segment]): Either[String, Task[Any]] = { + + remainingSelector match{ + case Mirror.Segment.Cross(_) :: Nil => Left("Selector cannot start with a [cross] segment") + case Mirror.Segment.Label(last) :: Nil => + def target = + hierarchy.targets + .find(_.label == last) + .map(x => Right(x.run(hierarchy.node(obj, remainingCrossSelectors)))) + + def invokeCommand[V](mirror: Mirror[T, V], name: String) = for{ + cmd <- mirror.commands.find(_.name == name) + } yield cmd.invoke( + mirror.node(obj, remainingCrossSelectors), + ammonite.main.Scripts.groupArgs(rest.toList) + ) match { + case Router.Result.Success(v) => Right(v) + case _ => Left(s"Command failed $last") + } + + def runDefault = for{ + (label, child) <- hierarchy.children + if label == last + res <- child.node(obj, remainingCrossSelectors) match{ + case taskMod: TaskModule => Some(invokeCommand(child, taskMod.defaultCommandName())) + case _ => None + } + } yield res + + def command = invokeCommand(hierarchy, last) + + command orElse target orElse runDefault.headOption.flatten match{ + case None => Left("Cannot resolve task " + Mirror.renderSelector( + (Mirror.Segment.Label(last) :: revSelectorsSoFar).reverse) + ) + case Some(either) => either + } + + + case head :: tail => + val newRevSelectorsSoFar = head :: revSelectorsSoFar + head match{ + case Mirror.Segment.Label(singleLabel) => + hierarchy.children.collectFirst{ + case (label, child) if label == singleLabel => child + } match{ + case Some(child) => resolve(tail, child, obj, rest, remainingCrossSelectors, newRevSelectorsSoFar) + case None => Left("Cannot resolve module " + Mirror.renderSelector(newRevSelectorsSoFar.reverse)) + } + + case Mirror.Segment.Cross(cross) => + val Some((crossGen, childMirror)) = hierarchy.crossChildren + val crossOptions = crossGen(hierarchy.node(obj, remainingCrossSelectors)) + if (crossOptions.contains(cross)){ + resolve(tail, childMirror, obj, rest, remainingCrossSelectors, newRevSelectorsSoFar) + }else{ + Left("Cannot resolve cross " + Mirror.renderSelector(newRevSelectorsSoFar.reverse)) + } + + + } + + case Nil => Left("Selector cannot be empty") + } + } +} diff --git a/core/src/test/scala/mill/main/MainTests.scala b/core/src/test/scala/mill/main/MainTests.scala index a4fd7011..4955112d 100644 --- a/core/src/test/scala/mill/main/MainTests.scala +++ b/core/src/test/scala/mill/main/MainTests.scala @@ -15,7 +15,7 @@ object MainTests extends TestSuite{ val resolved = for{ args <- mill.Main.parseArgs(selectorString) val crossSelectors = args.map{case Mirror.Segment.Cross(x) => x.toList.map(_.toString) case _ => Nil} - task <- mill.Main.resolve(args, mirror, obj, Nil, crossSelectors, Nil) + task <- mill.main.Resolve.resolve(args, mirror, obj, Nil, crossSelectors, Nil) } yield task assert(resolved == expected) } -- cgit v1.2.3