diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2017-12-24 00:13:01 -0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2017-12-24 18:25:41 -0800 |
commit | 981b7191a2a666447c85c6632d5a2c626d19a849 (patch) | |
tree | 6b2c77ede5777a3eb07e18dbb3139a2c535f5cb3 /core/src/main/scala | |
parent | 4181484c6a3674788e1a57f4d4d1fe28ffabbce3 (diff) | |
download | mill-981b7191a2a666447c85c6632d5a2c626d19a849.tar.gz mill-981b7191a2a666447c85c6632d5a2c626d19a849.tar.bz2 mill-981b7191a2a666447c85c6632d5a2c626d19a849.zip |
Remove `Router.scala`, whose changes have been upstreamed into the Ammonite master branch
Remove the `out/run.sc` entrypoint script, using Ammonite's new `codeWrapper` API to synthesize the necessary wrapper/forwarder objects to substitute it
Diffstat (limited to 'core/src/main/scala')
-rw-r--r-- | core/src/main/scala/mill/Main.scala | 105 | ||||
-rw-r--r-- | core/src/main/scala/mill/discover/Discovered.scala | 8 | ||||
-rw-r--r-- | core/src/main/scala/mill/discover/Mirror.scala | 2 | ||||
-rw-r--r-- | core/src/main/scala/mill/discover/Router.scala | 410 | ||||
-rw-r--r-- | core/src/main/scala/mill/main/Resolve.scala | 7 |
5 files changed, 85 insertions, 447 deletions
diff --git a/core/src/main/scala/mill/Main.scala b/core/src/main/scala/mill/Main.scala index 86ee8649..761430e4 100644 --- a/core/src/main/scala/mill/Main.scala +++ b/core/src/main/scala/mill/Main.scala @@ -112,28 +112,6 @@ object Main { watch: Boolean = false) def main(args: Array[String]): Unit = { - val syntheticPath = pwd / 'out / "run.sc" - write.over( - syntheticPath, - s"""import $$file.^.build - |import mill._ - | - |val mapping = mill.discover.Discovered.mapping(build) - | - |mill.Main.consistencyCheck(mapping).left.foreach(msg => throw new Exception(msg)) - | - |@main def run(args: String*) = mill.Main(args, mapping, interp.watch, true) - | - |@main def idea() = mill.scalaplugin.GenIdea(mapping) - | - |val evaluator = new mill.eval.Evaluator( - | ammonite.ops.pwd / 'out, - | mapping.value, - | new mill.util.PrintLogger(true) - |) - | - |implicit val replApplyHandler = new mill.main.ReplApplyHandler(evaluator)""".stripMargin - ) import ammonite.main.Cli var repl = false @@ -158,8 +136,8 @@ object Main { val config = if(!repl) cliConfig else cliConfig.copy( - predefFile = Some(pwd / 'out / "run.sc"), - predefCode = "import build._", + defaultPredef = false, + predefFile = Some(pwd/"build.sc"), welcomeBanner = None ) @@ -169,7 +147,7 @@ object Main { System.in, System.out, System.err ){ override def initMain(isRepl: Boolean) = { - super.initMain(isRepl).copy(codeWrapper = customCodeWrapper) + super.initMain(isRepl).copy(scriptCodeWrapper = customCodeWrapper) } } @@ -177,23 +155,86 @@ object Main { runner.printInfo("Loading...") runner.runRepl() } else { - runner.runScript(syntheticPath, leftoverArgs) + runner.runScript(pwd / "build.sc", leftoverArgs) } } } val customCodeWrapper = new Preprocessor.CodeWrapper { def top(pkgName: Seq[Name], imports: Imports, indexedWrapperName: Name) = { - normalizeNewlines(s""" -package ${pkgName.head.encoded} -package ${Util.encodeScalaSourcePath(pkgName.tail)} -$imports - -object ${indexedWrapperName.backticked} extends mill.Module{\n""") + s""" + |package ${pkgName.head.encoded} + |package ${Util.encodeScalaSourcePath(pkgName.tail)} + |$imports + |import mill._ + |sealed abstract class ${indexedWrapperName.backticked} extends mill.Module{\n + |""".stripMargin } def bottom(printCode: String, indexedWrapperName: Name, extraCode: String) = { + val wrapName = indexedWrapperName.backticked + val tmpName = ammonite.util.Name(indexedWrapperName.raw + "-Temp").backticked + + // Define `discovered` in the `tmpName` trait, before mixing in `MainWrapper`, + // to ensure that `$tempName#discovered` is initialized before `MainWrapper` is. + // + // `import $wrapName._` is necessary too let Ammonite pick up all the + // members of class wrapper, which are inherited but otherwise not visible + // in the AST of the `$wrapName` object + // + // We need to duplicate the Ammonite predef as part of the wrapper because + // imports within the body of the class wrapper are not brought into scope + // by the `import $wrapName._`. Other non-Ammonite-predef imports are not + // made available, and that's just too bad + s""" + |} + |trait $tmpName{ + | val discovered = mill.discover.Discovered.make[$wrapName] + | val interpApi = ammonite.interp.InterpBridge.value + |} + | + |object $wrapName + |extends $wrapName + |with $tmpName + |with mill.MainWrapper[$wrapName] { + | @ammonite.main.Router.main + | def idea() = mill.scalaplugin.GenIdea(mapping) + | ${ammonite.main.Defaults.replPredef} + | ${ammonite.main.Defaults.predefString} + | ${ammonite.Main.extraPredefString} + | import ammonite.repl.ReplBridge.{value => repl} + | import ammonite.interp.InterpBridge.{value => interp} + | import $wrapName._ + """.stripMargin + Preprocessor.CodeWrapper.bottom(printCode, indexedWrapperName, extraCode) } } } +/** + * Class that wraps each Mill build file. + */ +trait MainWrapper[T]{ + val discovered: mill.discover.Discovered[T] + val interpApi: ammonite.interp.InterpAPI + val mapping = discovered.mapping(this.asInstanceOf[T]) + + mill.Main.consistencyCheck(mapping).left.foreach(msg => throw new Exception(msg)) + + @ammonite.main.Router.main + def run(args: String*) = mill.Main( + args, + mapping, + interpApi.watch, + true + ) + + + val evaluator = new mill.eval.Evaluator( + ammonite.ops.pwd / 'out, + mapping.value, + new mill.util.PrintLogger(true) + ) + + implicit val replApplyHandler: mill.main.ReplApplyHandler = + new mill.main.ReplApplyHandler(evaluator) +}
\ No newline at end of file diff --git a/core/src/main/scala/mill/discover/Discovered.scala b/core/src/main/scala/mill/discover/Discovered.scala index 0034a877..1f27c771 100644 --- a/core/src/main/scala/mill/discover/Discovered.scala +++ b/core/src/main/scala/mill/discover/Discovered.scala @@ -3,7 +3,8 @@ package mill.discover import mill.define.Task.Module import mill.define.{Cross, Target, Task} import mill.discover.Mirror.LabelledTarget -import mill.discover.Router.{EntryPoint, Result} +import ammonite.main.Router +import ammonite.main.Router.{EntryPoint, Result} import scala.language.experimental.macros import scala.reflect.macros.blackbox.Context @@ -103,7 +104,10 @@ object Discovered { } val commands = - r.getAllRoutesForClass(t.asInstanceOf[r.c.Type]) + r.getAllRoutesForClass( + t.asInstanceOf[r.c.Type], + _.returnType <:< weakTypeOf[mill.define.Command[_]].asInstanceOf[r.c.Type] + ) .asInstanceOf[Seq[c.Tree]] .toList diff --git a/core/src/main/scala/mill/discover/Mirror.scala b/core/src/main/scala/mill/discover/Mirror.scala index 41b3a0fe..926d9669 100644 --- a/core/src/main/scala/mill/discover/Mirror.scala +++ b/core/src/main/scala/mill/discover/Mirror.scala @@ -1,7 +1,7 @@ package mill.discover import mill.define.{Target, Task} -import mill.discover.Router.EntryPoint +import ammonite.main.Router.EntryPoint import scala.language.experimental.macros diff --git a/core/src/main/scala/mill/discover/Router.scala b/core/src/main/scala/mill/discover/Router.scala deleted file mode 100644 index b2d222b9..00000000 --- a/core/src/main/scala/mill/discover/Router.scala +++ /dev/null @@ -1,410 +0,0 @@ -package mill.discover - -import ammonite.main.Compat -import mill.define.Task -import mill.discover.Router.ArgSig -import sourcecode.Compat.Context - -import scala.annotation.StaticAnnotation -import scala.collection.mutable -import scala.language.experimental.macros -/** - * More or less a minimal version of Autowire's Server that lets you generate - * a set of "routes" from the methods defined in an object, and call them - * using passing in name/args/kwargs via Java reflection, without having to - * generate/compile code or use Scala reflection. This saves us spinning up - * the Scala compiler and greatly reduces the startup time of cached scripts. - */ -object Router{ - class doc(s: String) extends StaticAnnotation - def generateRoutes[T]: Seq[Router.EntryPoint[T]] = macro generateRoutesImpl[T] - def generateRoutesImpl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[EntryPoint[T]]] = { - import c.universe._ - val r = new Router(c) - val allRoutes = r.getAllRoutesForClass( - weakTypeOf[T].asInstanceOf[r.c.Type], - ).asInstanceOf[Iterable[c.Tree]] - - c.Expr[Seq[EntryPoint[T]]](q"_root_.scala.Seq(..$allRoutes)") - } - - /** - * Models what is known by the router about a single argument: that it has - * a [[name]], a human-readable [[typeString]] describing what the type is - * (just for logging and reading, not a replacement for a `TypeTag`) and - * possible a function that can compute its default value - */ - case class ArgSig[T](name: String, - typeString: String, - doc: Option[String], - default: Option[T => Any]) - - def stripDashes(s: String) = { - if (s.startsWith("--")) s.drop(2) - else if (s.startsWith("-")) s.drop(1) - else s - } - /** - * What is known about a single endpoint for our routes. It has a [[name]], - * [[argSignatures]] for each argument, and a macro-generated [[invoke0]] - * that performs all the necessary argument parsing and de-serialization. - * - * Realistically, you will probably spend most of your time calling [[invoke]] - * instead, which provides a nicer API to call it that mimmicks the API of - * calling a Scala method. - */ - case class EntryPoint[T](name: String, - argSignatures: Seq[ArgSig[T]], - doc: Option[String], - varargs: Boolean, - invoke0: (T, Map[String, String], Seq[String]) => Result[Task[Any]]){ - def invoke(target: T, groupedArgs: Seq[(String, Option[String])]): Result[Task[Any]] = { - var remainingArgSignatures = argSignatures.toList - - - val accumulatedKeywords = mutable.Map.empty[ArgSig[T], mutable.Buffer[String]] - val keywordableArgs = if (varargs) argSignatures.dropRight(1) else argSignatures - - for(arg <- keywordableArgs) accumulatedKeywords(arg) = mutable.Buffer.empty - - val leftoverArgs = mutable.Buffer.empty[String] - - val lookupArgSig = argSignatures.map(x => (x.name, x)).toMap - - var incomplete: Option[ArgSig[T]] = None - - for(group <- groupedArgs){ - - group match{ - case (value, None) => - if (value(0) == '-' && !varargs){ - lookupArgSig.get(stripDashes(value)) match{ - case None => leftoverArgs.append(value) - case Some(sig) => incomplete = Some(sig) - } - - } else remainingArgSignatures match { - case Nil => leftoverArgs.append(value) - case last :: Nil if varargs => leftoverArgs.append(value) - case next :: rest => - accumulatedKeywords(next).append(value) - remainingArgSignatures = rest - } - case (rawKey, Some(value)) => - val key = stripDashes(rawKey) - lookupArgSig.get(key) match{ - case Some(x) if accumulatedKeywords.contains(x) => - if (accumulatedKeywords(x).nonEmpty && varargs){ - leftoverArgs.append(rawKey, value) - }else{ - accumulatedKeywords(x).append(value) - remainingArgSignatures = remainingArgSignatures.filter(_.name != key) - } - case _ => - leftoverArgs.append(rawKey, value) - } - } - } - - val missing0 = remainingArgSignatures.filter(_.default.isEmpty) - val missing = if(varargs) { - missing0.filter(_ != argSignatures.last) - } else { - missing0.filter(x => incomplete != Some(x)) - } - val duplicates = accumulatedKeywords.toSeq.filter(_._2.length > 1) - - if ( - incomplete.nonEmpty || - missing.nonEmpty || - duplicates.nonEmpty || - (leftoverArgs.nonEmpty && !varargs) - ){ - Result.Error.MismatchedArguments( - missing = missing, - unknown = leftoverArgs, - duplicate = duplicates, - incomplete = incomplete - - ) - } else { - val mapping = accumulatedKeywords - .iterator - .collect{case (k, Seq(single)) => (k.name, single)} - .toMap - - try invoke0(target, mapping, leftoverArgs) - catch{case e: Throwable => - Result.Error.Exception(e) - } - } - } - } - - def tryEither[T](t: => T, error: Throwable => Result.ParamError) = { - try Right(t) - catch{ case e: Throwable => Left(error(e))} - } - def readVarargs[T](arg: ArgSig[_], - values: Seq[String], - thunk: String => T) = { - val attempts = - for(item <- values) - yield tryEither(thunk(item), Result.ParamError.Invalid(arg, item, _)) - - - val bad = attempts.collect{ case Left(x) => x} - if (bad.nonEmpty) Left(bad) - else Right(attempts.collect{case Right(x) => x}) - } - def read[T](dict: Map[String, String], - default: => Option[Any], - arg: ArgSig[_], - thunk: String => T): FailMaybe = { - dict.get(arg.name) match{ - case None => - tryEither(default.get, Result.ParamError.DefaultFailed(arg, _)).left.map(Seq(_)) - - case Some(x) => - tryEither(thunk(x), Result.ParamError.Invalid(arg, x, _)).left.map(Seq(_)) - } - } - - /** - * Represents what comes out of an attempt to invoke an [[EntryPoint]]. - * Could succeed with a value, but could fail in many different ways. - */ - sealed trait Result[+T] - object Result{ - - /** - * Invoking the [[EntryPoint]] was totally successful, and returned a - * result - */ - case class Success[T](value: T) extends Result[T] - - /** - * Invoking the [[EntryPoint]] was not successful - */ - sealed trait Error extends Result[Nothing] - object Error{ - - /** - * Invoking the [[EntryPoint]] failed with an exception while executing - * code within it. - */ - case class Exception(t: Throwable) extends Error - - /** - * Invoking the [[EntryPoint]] failed because the arguments provided - * did not line up with the arguments expected - */ - case class MismatchedArguments(missing: Seq[ArgSig[_]], - unknown: Seq[String], - duplicate: Seq[(ArgSig[_], Seq[String])], - incomplete: Option[ArgSig[_]]) extends Error - /** - * Invoking the [[EntryPoint]] failed because there were problems - * deserializing/parsing individual arguments - */ - case class InvalidArguments(values: Seq[ParamError]) extends Error - } - - sealed trait ParamError - object ParamError{ - /** - * Something went wrong trying to de-serialize the input parameter; - * the thrown exception is stored in [[ex]] - */ - case class Invalid(arg: ArgSig[_], value: String, ex: Throwable) extends ParamError - /** - * Something went wrong trying to evaluate the default value - * for this input parameter - */ - case class DefaultFailed(arg: ArgSig[_], ex: Throwable) extends ParamError - } - } - - - type FailMaybe = Either[Seq[Result.ParamError], Any] - type FailAll = Either[Seq[Result.ParamError], Seq[Any]] - - def validate(args: Seq[FailMaybe]): Result[Seq[Any]] = { - val lefts = args.collect{case Left(x) => x}.flatten - - if (lefts.nonEmpty) Result.Error.InvalidArguments(lefts) - else { - val rights = args.collect{case Right(x) => x} - Result.Success(rights) - } - } - - def makeReadCall[T: scopt.Read](dict: Map[String, String], - default: => Option[Any], - arg: ArgSig[_]) = { - read[T](dict, default, arg, implicitly[scopt.Read[T]].reads(_)) - } - def makeReadVarargsCall[T: scopt.Read](arg: ArgSig[_], - values: Seq[String]) = { - readVarargs[T](arg, values, implicitly[scopt.Read[T]].reads(_)) - } -} - -class Router [C <: Context](val c: C) { - import c.universe._ - def getValsOrMeths(curCls: Type): Iterable[MethodSymbol] = { - def isAMemberOfAnyRef(member: Symbol) = { - // AnyRef is an alias symbol, we go to the real "owner" of these methods - val anyRefSym = c.mirror.universe.definitions.ObjectClass - member.owner == anyRefSym - } - val extractableMembers = for { - member <- curCls.members - if !isAMemberOfAnyRef(member) - if !member.isSynthetic - if member.isPublic - if member.isTerm - memTerm = member.asTerm - if memTerm.isMethod - if !memTerm.isModule - } yield memTerm.asMethod - - extractableMembers flatMap { case memTerm => - if (memTerm.isSetter || memTerm.isConstructor || memTerm.isGetter) Nil - else Seq(memTerm) - - } - } - - - - def extractMethod(meth: MethodSymbol, curCls: c.universe.Type): c.universe.Tree = { - val flattenedArgLists = meth.paramss.flatten - def hasDefault(i: Int) = { - val defaultName = s"${meth.name}$$default$$${i + 1}" - if (curCls.members.exists(_.name.toString == defaultName)) Some(defaultName) - else None - } - val argListSymbol = q"${c.fresh[TermName]("argsList")}" - val extrasSymbol = q"${c.fresh[TermName]("extras")}" - val defaults = for ((arg, i) <- flattenedArgLists.zipWithIndex) yield { - val arg = TermName(c.freshName()) - hasDefault(i).map(defaultName => q"($arg: $curCls) => $arg.${newTermName(defaultName)}") - } - - def getDocAnnotation(annotations: List[Annotation]) = { - val (docTrees, remaining) = annotations.partition(_.tpe =:= typeOf[Router.doc]) - val docValues = for { - doc <- docTrees - if doc.scalaArgs.head.isInstanceOf[Literal] - l = doc.scalaArgs.head.asInstanceOf[Literal] - if l.value.value.isInstanceOf[String] - } yield l.value.value.asInstanceOf[String] - (remaining, docValues.headOption) - } - - def unwrapVarargType(arg: Symbol) = { - val vararg = arg.typeSignature.typeSymbol == definitions.RepeatedParamClass - val unwrappedType = - if (!vararg) arg.typeSignature - else arg.typeSignature.asInstanceOf[TypeRef].args(0) - - (vararg, unwrappedType) - } - - - val (_, methodDoc) = getDocAnnotation(meth.annotations) - val readArgSigs = for( - ((arg, defaultOpt), i) <- flattenedArgLists.zip(defaults).zipWithIndex - ) yield { - - val (vararg, varargUnwrappedType) = unwrapVarargType(arg) - - val default = - if (vararg) q"scala.Some(scala.Nil)" - else defaultOpt match { - case Some(defaultExpr) => q"scala.Some($defaultExpr())" - case None => q"scala.None" - } - - val (docUnwrappedType, docOpt) = varargUnwrappedType match{ - case t: AnnotatedType => - - val (remaining, docValue) = getDocAnnotation(t.annotations) - if (remaining.isEmpty) (t.underlying, docValue) - else (Compat.copyAnnotatedType(c)(t, remaining), docValue) - - case t => (t, None) - } - - val docTree = docOpt match{ - case None => q"scala.None" - case Some(s) => q"scala.Some($s)" - } - val argSig = q""" - mill.discover.Router.ArgSig( - ${arg.name.toString}, - ${docUnwrappedType.toString + (if(vararg) "*" else "")}, - $docTree, - $defaultOpt - ) - """ - - val reader = - if(vararg) q""" - mill.discover.Router.makeReadVarargsCall[$docUnwrappedType]( - $argSig, - $extrasSymbol - ) - """ else q""" - mill.discover.Router.makeReadCall[$docUnwrappedType]( - $argListSymbol, - $default, - $argSig - ) - """ - c.internal.setPos(reader, meth.pos) - (reader, argSig, vararg) - } - - val (readArgs, argSigs, varargs) = readArgSigs.unzip3 - val (argNames, argNameCasts) = flattenedArgLists.map { arg => - val (vararg, unwrappedType) = unwrapVarargType(arg) - ( - pq"${arg.name.toTermName}", - if (!vararg) q"${arg.name.toTermName}.asInstanceOf[$unwrappedType]" - else q"${arg.name.toTermName}.asInstanceOf[Seq[$unwrappedType]]: _*" - - ) - }.unzip - - val arg = TermName(c.freshName()) - q""" - mill.discover.Router.EntryPoint( - ${meth.name.toString}, - scala.Seq(..$argSigs), - ${methodDoc match{ - case None => q"scala.None" - case Some(s) => q"scala.Some($s)" - }}, - ${varargs.contains(true)}, - ($arg: $curCls, $argListSymbol: Map[String, String], $extrasSymbol: Seq[String]) => - mill.discover.Router.validate(Seq(..$readArgs)) match{ - case mill.discover.Router.Result.Success(List(..$argNames)) => - mill.discover.Router.Result.Success($arg.${meth.name.toTermName}(..$argNameCasts)) - case x: mill.discover.Router.Result.Error => x - } - ) - """ - } - - def getAllRoutesForClass(curCls: Type): Iterable[c.universe.Tree] = { - for{ - t <- getValsOrMeths(curCls) - if t.returnType <:< typeOf[mill.define.Command[_]] - } yield { - extractMethod(t, curCls) - } - } -} - diff --git a/core/src/main/scala/mill/main/Resolve.scala b/core/src/main/scala/mill/main/Resolve.scala index 9cfc33b5..6196cdc6 100644 --- a/core/src/main/scala/mill/main/Resolve.scala +++ b/core/src/main/scala/mill/main/Resolve.scala @@ -2,7 +2,8 @@ package mill.main import mill.define.Task import mill.define.Task.TaskModule -import mill.discover.{Mirror, Router} +import mill.discover.{Mirror} +import ammonite.main.Router object Resolve { def resolve[T, V](remainingSelector: List[Mirror.Segment], @@ -45,7 +46,9 @@ object Resolve { case None => Left("Cannot resolve task " + Mirror.renderSelector( (Mirror.Segment.Label(last) :: revSelectorsSoFar).reverse) ) - case Some(either) => either + // Contents of `either` *must* be a `Task`, because we only select + // methods returning `Task` in the discovery process + case Some(either) => either.right.map{ case x: Task[Any] => x } } |