diff options
26 files changed, 512 insertions, 181 deletions
diff --git a/ci/test-mill-built.sh b/ci/test-mill-built.sh index 5b3b1a28..52be8851 100755 --- a/ci/test-mill-built.sh +++ b/ci/test-mill-built.sh @@ -13,7 +13,7 @@ target/bin/mill devAssembly # Second build & run tests using Mill -out/devAssembly/dest/out.jar --all {core,scalalib,scalajslib}.test devAssembly +out/devAssembly/dest/out.jar all {core,scalalib,scalajslib}.test devAssembly out/devAssembly/dest/out.jar integration.test mill.integration.AmmoniteTests out/devAssembly/dest/out.jar integration.test "mill.integration.{AcyclicTests,BetterFilesTests,JawnTests}" out/devAssembly/dest/out.jar devAssembly diff --git a/ci/test-mill-release.sh b/ci/test-mill-release.sh index 5e19bcdc..e3755e93 100755 --- a/ci/test-mill-release.sh +++ b/ci/test-mill-release.sh @@ -9,7 +9,7 @@ git clean -xdf sbt bin/test:assembly # Build Mill using SBT -target/bin/mill --all _.publishLocal releaseAssembly +target/bin/mill all __.publishLocal releaseAssembly mv out/releaseAssembly/dest/out.jar ~/mill-release @@ -17,7 +17,7 @@ git clean -xdf # Second build & run tests using Mill -~/mill-release --all {core,scalalib,scalajslib}.test devAssembly +~/mill-release all {core,scalalib,scalajslib}.test devAssembly ~/mill-release integration.test mill.integration.AmmoniteTests ~/mill-release integration.test "mill.integration.{AcyclicTests,BetterFilesTests,JawnTests}" ~/mill-release devAssembly diff --git a/ci/test-sbt-built.sh b/ci/test-sbt-built.sh index ebb3ddb5..3a60cbd3 100755 --- a/ci/test-sbt-built.sh +++ b/ci/test-sbt-built.sh @@ -8,7 +8,7 @@ git clean -xdf sbt bin/test:assembly # Run tests using Mill built using SBT -target/bin/mill --all {core,scalalib,scalajslib}.test devAssembly +target/bin/mill all {core,scalalib,scalajslib}.test devAssembly target/bin/mill integration.test mill.integration.AmmoniteTests target/bin/mill integration.test "mill.integration.{AcyclicTests,BetterFilesTests,JawnTests}" target/bin/mill devAssembly diff --git a/ci/test_all.sh b/ci/test_all.sh index 74b6d203..a04f7119 100755 --- a/ci/test_all.sh +++ b/ci/test_all.sh @@ -9,7 +9,7 @@ git clean -xdf sbt core/test scalalib/test scalajslib/test integration/test bin/test:assembly # Run tests using Mill built using SBT -target/bin/mill --all {core,scalalib,scalajslib,integration}.test devAssembly +target/bin/mill all {core,scalalib,scalajslib,integration}.test devAssembly # Second build & run tests using Mill -out/devAssembly/dest/out.jar --all {core,scalalib,scalajslib,integration}.test devAssembly
\ No newline at end of file +out/devAssembly/dest/out.jar all {core,scalalib,scalajslib,integration}.test devAssembly
\ No newline at end of file diff --git a/core/src/mill/define/BaseModule.scala b/core/src/mill/define/BaseModule.scala index cedfcef7..5253e691 100644 --- a/core/src/mill/define/BaseModule.scala +++ b/core/src/mill/define/BaseModule.scala @@ -1,6 +1,6 @@ package mill.define -import ammonite.main.Router.Overrides +import mill.main.Router.Overrides import ammonite.ops.Path import mill.main.ParseArgs @@ -31,7 +31,6 @@ abstract class BaseModule(millSourcePath0: Path, external0: Boolean = false) override implicit def millModuleBasePath: BasePath = BasePath(millSourcePath) implicit def millImplicitBaseModule: BaseModule.Implicit = BaseModule.Implicit(this) def millDiscover: Discover[this.type] - implicit def millScoptTargetReads[T] = new TargetScopt[T]() } @@ -49,43 +48,3 @@ abstract class ExternalModule(implicit millModuleEnclosing0: sourcecode.Enclosin Segments(millModuleEnclosing0.value.split('.').map(Segment.Label):_*) } } - -object TargetScopt{ - case class Targets[T](items: Seq[mill.define.Target[T]]) - implicit def millScoptTargetReads[T] = new TargetScopt[T]() - // This needs to be a ThreadLocal because we need to pass it into the body of - // the TargetScopt#read call, which does not accept additional parameters. - // Until we migrate our CLI parsing off of Scopt (so we can pass the BaseModule - // in directly) we are forced to pass it in via a ThreadLocal - val currentRootModule = new ThreadLocal[BaseModule] -} -class TargetScopt[T]() - extends scopt.Read[TargetScopt.Targets[T]]{ - def arity = 1 - def reads = s => try{ - val rootModule = TargetScopt.currentRootModule.get - val d = rootModule.millDiscover - val (expanded, Nil) = ParseArgs(Seq("--all", s)).fold(e => throw new Exception(e), identity) - - val resolved = expanded.map{ - case (Some(scoping), segments) => - val moduleCls = rootModule.getClass.getClassLoader.loadClass(scoping.render + "$") - val externalRootModule = moduleCls.getField("MODULE$").get(moduleCls).asInstanceOf[ExternalModule] - val crossSelectors = segments.value.map { - case mill.define.Segment.Cross(x) => x.toList.map(_.toString) - case _ => Nil - } - mill.main.Resolve.resolve(segments.value.toList, externalRootModule, d, Nil, crossSelectors.toList, Nil) - case (None, segments) => - val crossSelectors = segments.value.map { - case mill.define.Segment.Cross(x) => x.toList.map(_.toString) - case _ => Nil - } - mill.main.Resolve.resolve(segments.value.toList, rootModule, d, Nil, crossSelectors.toList, Nil) - } - mill.util.EitherOps.sequence(resolved) match{ - case Left(s) => throw new Exception(s) - case Right(ts) => TargetScopt.Targets(ts.flatten.collect{case t: mill.define.Target[T] => t}) - } - }catch{case e => e.printStackTrace(); throw e} -} diff --git a/core/src/mill/define/Ctx.scala b/core/src/mill/define/Ctx.scala index 1e85d8b3..11e9e1f5 100644 --- a/core/src/mill/define/Ctx.scala +++ b/core/src/mill/define/Ctx.scala @@ -1,6 +1,6 @@ package mill.define -import ammonite.main.Router.Overrides +import mill.main.Router.Overrides import ammonite.ops.{Path, RelPath} import scala.annotation.implicitNotFound @@ -67,7 +67,7 @@ object Ctx{ millName0: sourcecode.Name, millModuleBasePath0: BasePath, segments0: Segments, - overrides0: Overrides, + overrides0: mill.main.Router.Overrides, external0: External): Ctx = { Ctx( millModuleEnclosing0.value, diff --git a/core/src/mill/define/Discover.scala b/core/src/mill/define/Discover.scala index b213d9f3..fd5bd449 100644 --- a/core/src/mill/define/Discover.scala +++ b/core/src/mill/define/Discover.scala @@ -1,6 +1,6 @@ package mill.define import language.experimental.macros -import ammonite.main.Router.{EntryPoint, Overrides} +import mill.main.Router.{EntryPoint, Overrides} import sourcecode.Compat.Context import scala.collection.mutable @@ -41,7 +41,7 @@ object Discover { } rec(weakTypeOf[T]) - val router = new mill.define.Router(c) + val router = new mill.main.Router(c) val mapping = for{ discoveredModuleType <- seen val curCls = discoveredModuleType.asInstanceOf[router.c.Type] @@ -57,7 +57,7 @@ object Discover { val (overrides, routes) = overridesRoutes.unzip val lhs = q"classOf[${discoveredModuleType.typeSymbol.asClass}]" val clsType = discoveredModuleType.typeSymbol.asClass - val rhs = q"scala.Seq[(Int, ammonite.main.Router.EntryPoint[_])](..$overridesRoutes)" + val rhs = q"scala.Seq[(Int, mill.main.Router.EntryPoint[_])](..$overridesRoutes)" q"$lhs -> $rhs" } diff --git a/core/src/mill/define/Module.scala b/core/src/mill/define/Module.scala index 66132fa9..a53ed345 100644 --- a/core/src/mill/define/Module.scala +++ b/core/src/mill/define/Module.scala @@ -2,7 +2,7 @@ package mill.define import java.lang.reflect.Modifier -import ammonite.main.Router.{EntryPoint, Overrides} +import mill.main.Router.{EntryPoint, Overrides} import ammonite.ops.Path import scala.language.experimental.macros diff --git a/core/src/mill/eval/Evaluator.scala b/core/src/mill/eval/Evaluator.scala index 3d8e82b8..347ad321 100644 --- a/core/src/mill/eval/Evaluator.scala +++ b/core/src/mill/eval/Evaluator.scala @@ -2,7 +2,7 @@ package mill.eval import java.net.URLClassLoader -import ammonite.main.Router.EntryPoint +import mill.main.Router.EntryPoint import ammonite.ops._ import ammonite.runtime.SpecialClassLoader import mill.define.{Ctx => _, _} @@ -58,13 +58,9 @@ class Evaluator[T](val outPath: Path, } } } - pprint.log(discover.value.keySet) - pprint.log(c.cls) findMatching(c.cls) match{ case Some(v) => - pprint.log(v) - pprint.log(c.ctx.segment.pathSegments) v.find(_._2.name == c.ctx.segment.pathSegments.head).get._1 // For now we don't properly support overrides for external modules // that do not appear in the Evaluator's main Discovered listing @@ -334,12 +330,6 @@ class Evaluator[T](val outPath: Path, object Evaluator{ - class Scopt extends scopt.Read[Evaluator[_]] { - def arity = 0 - def reads = _ => dynamicScopt.get - } - val dynamicScopt = new ThreadLocal[Evaluator[_]] - implicit def evaluatorScopt = new Scopt case class Paths(out: Path, dest: Path, meta: Path, diff --git a/core/src/mill/main/MagicScopt.scala b/core/src/mill/main/MagicScopt.scala new file mode 100644 index 00000000..acba57cb --- /dev/null +++ b/core/src/mill/main/MagicScopt.scala @@ -0,0 +1,49 @@ +package mill.main +import mill.define.ExternalModule +import mill.main.ParseArgs + +object MagicScopt{ + // This needs to be a ThreadLocal because we need to pass it into the body of + // the TargetScopt#read call, which does not accept additional parameters. + // Until we migrate our CLI parsing off of Scopt (so we can pass the BaseModule + // in directly) we are forced to pass it in via a ThreadLocal + val currentEvaluator = new ThreadLocal[mill.eval.Evaluator[_]] + + case class Tasks[T](items: Seq[mill.define.NamedTask[T]]) +} +class EvaluatorScopt[T]() + extends scopt.Read[mill.eval.Evaluator[T]]{ + def arity = 0 + def reads = s => try{ + MagicScopt.currentEvaluator.get.asInstanceOf[mill.eval.Evaluator[T]] + } +} +class TargetScopt[T]() + extends scopt.Read[MagicScopt.Tasks[T]]{ + def arity = 0 + def reads = s => { + val rootModule = MagicScopt.currentEvaluator.get.rootModule + val d = rootModule.millDiscover + val (expanded, leftover) = ParseArgs(Seq(s)).fold(e => throw new Exception(e), identity) + val resolved = expanded.map{ + case (Some(scoping), segments) => + val moduleCls = rootModule.getClass.getClassLoader.loadClass(scoping.render + "$") + val externalRootModule = moduleCls.getField("MODULE$").get(moduleCls).asInstanceOf[ExternalModule] + val crossSelectors = segments.value.map { + case mill.define.Segment.Cross(x) => x.toList.map(_.toString) + case _ => Nil + } + mill.main.Resolve.resolve(segments.value.toList, externalRootModule, d, leftover, crossSelectors.toList, Nil) + case (None, segments) => + val crossSelectors = segments.value.map { + case mill.define.Segment.Cross(x) => x.toList.map(_.toString) + case _ => Nil + } + mill.main.Resolve.resolve(segments.value.toList, rootModule, d, leftover, crossSelectors.toList, Nil) + } + mill.util.EitherOps.sequence(resolved) match{ + case Left(s) => throw new Exception(s) + case Right(ts) => MagicScopt.Tasks(ts.flatten).asInstanceOf[MagicScopt.Tasks[T]] + } + } +}
\ No newline at end of file diff --git a/core/src/mill/main/MainModule.scala b/core/src/mill/main/MainModule.scala new file mode 100644 index 00000000..fd46fb77 --- /dev/null +++ b/core/src/mill/main/MainModule.scala @@ -0,0 +1,27 @@ +package mill.main + +trait MainModule extends mill.Module{ + implicit def millDiscover: mill.define.Discover[_] + implicit def millScoptTargetReads[T] = new mill.main.TargetScopt[T]() + implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() + def resolve(targets: mill.main.MagicScopt.Tasks[Any]*) = mill.T.command{ + targets.flatMap(_.items).foreach(println) + } + def all(evaluator: mill.eval.Evaluator[Any], + targets: mill.main.MagicScopt.Tasks[Any]*) = mill.T.command{ + val (watched, res) = mill.main.RunScript.evaluate( + evaluator, + mill.util.Strict.Agg.from(targets.flatMap(_.items)) + ) + } + def show(evaluator: mill.eval.Evaluator[Any], + targets: mill.main.MagicScopt.Tasks[Any]*) = mill.T.command{ + val (watched, res) = mill.main.RunScript.evaluate( + evaluator, + mill.util.Strict.Agg.from(targets.flatMap(_.items)) + ) + for(json <- res.right.get.flatMap(_._2)){ + println(json) + } + } +} diff --git a/core/src/mill/main/MainRunner.scala b/core/src/mill/main/MainRunner.scala index b61ab450..9004de39 100644 --- a/core/src/mill/main/MainRunner.scala +++ b/core/src/mill/main/MainRunner.scala @@ -91,7 +91,6 @@ class MainRunner(config: ammonite.main.Cli.Config, |package ${Util.encodeScalaSourcePath(pkgName.tail)} |$imports |import mill._ - |import mill.eval.Evaluator.evaluatorScopt |object $wrapName |extends mill.define.BaseModule(ammonite.ops.Path($literalPath)) |with $wrapName{ @@ -105,28 +104,7 @@ class MainRunner(config: ammonite.main.Cli.Config, | val millSelf = Some(this) |} | - |sealed trait $wrapName extends mill.Module{this: mill.define.BaseModule => - | def resolve(targets: mill.define.TargetScopt.Targets[Any]*) = mill.T.command{ - | targets.flatMap(_.items).foreach(println) - | } - | def all(evaluator: mill.eval.Evaluator[_], - | targets: mill.define.TargetScopt.Targets[Any]*) = mill.T.command{ - | val (watched, res) = mill.main.RunScript.evaluate( - | evaluator, - | mill.util.Strict.Agg.from(targets.flatMap(_.items)) - | ) - | } - | def show(evaluator: mill.eval.Evaluator[_], - | targets: mill.define.TargetScopt.Targets[Any]*) = mill.T.command{ - | val (watched, res) = mill.main.RunScript.evaluate( - | evaluator, - | mill.util.Strict.Agg.from(targets.flatMap(_.items)) - | ) - | for(json <- res.right.get.flatMap(_._2)){ - | println(json) - | } - | } - | implicit def millDiscover: mill.define.Discover[_] + |sealed trait $wrapName extends mill.main.MainModule{ |""".stripMargin } diff --git a/core/src/mill/main/Resolve.scala b/core/src/mill/main/Resolve.scala index 7ecd4ac8..1932c241 100644 --- a/core/src/mill/main/Resolve.scala +++ b/core/src/mill/main/Resolve.scala @@ -2,9 +2,8 @@ package mill.main import mill.define._ import mill.define.TaskModule -import ammonite.main.Router -import ammonite.main.Router.EntryPoint -import ammonite.util.Res +import mill.main.Router.EntryPoint +import ammonite.util.{Res} object Resolve { def resolve[T, V](remainingSelector: List[Segment], @@ -12,7 +11,7 @@ object Resolve { discover: Discover[_], rest: Seq[String], remainingCrossSelectors: List[List[String]], - revSelectorsSoFar: List[Segment]): Either[String, Seq[Task[Any]]] = { + revSelectorsSoFar: List[Segment]): Either[String, Seq[NamedTask[Any]]] = { remainingSelector match{ case Segment.Cross(_) :: Nil => Left("Selector cannot start with a [cross] segment") @@ -24,17 +23,25 @@ object Resolve { .find(_.label == last) .map(Right(_)) + def shimArgsig[T](a: mill.main.Router.ArgSig[T, _]) = { + ammonite.main.Router.ArgSig[T]( + a.name, + a.typeString, + a.doc, + a.default + ) + } def invokeCommand(target: mill.Module, name: String) = for{ (cls, entryPoints) <- discover.value if cls.isAssignableFrom(target.getClass) ep <- entryPoints if ep._2.name == name - } yield ammonite.main.Scripts.runMainMethod( + } yield mill.main.Scripts.runMainMethod( target, ep._2.asInstanceOf[EntryPoint[mill.Module]], ammonite.main.Scripts.groupArgs(rest.toList) ) match{ - case Res.Success(v) => Right(v) + case Res.Success(v: Command[_]) => Right(v) case Res.Failure(msg) => Left(msg) case Res.Exception(ex, msg) => val sw = new java.io.StringWriter() @@ -61,7 +68,7 @@ object Resolve { ) // 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] => Seq(x) } + case Some(either) => either.right.map(Seq(_)) } diff --git a/core/src/mill/define/Router.scala b/core/src/mill/main/Router.scala index 4a9c3ffb..935ffc72 100644 --- a/core/src/mill/define/Router.scala +++ b/core/src/mill/main/Router.scala @@ -1,8 +1,7 @@ -package mill.define +package mill.main import ammonite.main.Compat -import sourcecode.Compat.Context import scala.annotation.StaticAnnotation import scala.collection.mutable @@ -25,7 +24,7 @@ object Router{ implicit def generate: Overrides = macro impl def impl(c: Context): c.Tree = { import c.universe._ - q"new _root_.ammonite.main.Router.Overrides(${c.internal.enclosingOwner.overrides.length})" + q"new _root_.mill.main.Router.Overrides(${c.internal.enclosingOwner.overrides.length})" } } @@ -48,10 +47,11 @@ object Router{ * (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]) + case class ArgSig[T, V](name: String, + typeString: String, + doc: Option[String], + default: Option[T => V]) + (implicit val reads: scopt.Read[V]) def stripDashes(s: String) = { if (s.startsWith("--")) s.drop(2) @@ -68,25 +68,24 @@ object Router{ * calling a Scala method. */ case class EntryPoint[T](name: String, - argSignatures: Seq[ArgSig[T]], + argSignatures: Seq[ArgSig[T, _]], doc: Option[String], varargs: Boolean, invoke0: (T, Map[String, String], Seq[String]) => Result[Any], overrides: Int){ def invoke(target: T, groupedArgs: Seq[(String, Option[String])]): Result[Any] = { - var remainingArgSignatures = argSignatures.toList + var remainingArgSignatures = argSignatures.toList.filter(_.reads.arity > 0) - - val accumulatedKeywords = mutable.Map.empty[ArgSig[T], mutable.Buffer[String]] + 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 + val lookupArgSig = Map(argSignatures.map(x => (x.name, x)):_*) - var incomplete: Option[ArgSig[T]] = None + var incomplete: Option[ArgSig[T, _]] = None for(group <- groupedArgs){ @@ -121,7 +120,9 @@ object Router{ } } - val missing0 = remainingArgSignatures.filter(_.default.isEmpty) + val missing0 = remainingArgSignatures + .filter(_.default.isEmpty) + val missing = if(varargs) { missing0.filter(_ != argSignatures.last) } else { @@ -160,9 +161,9 @@ object Router{ try Right(t) catch{ case e: Throwable => Left(error(e))} } - def readVarargs[T](arg: ArgSig[_], - values: Seq[String], - thunk: String => T) = { + def readVarargs(arg: ArgSig[_, _], + values: Seq[String], + thunk: String => Any) = { val attempts = for(item <- values) yield tryEither(thunk(item), Result.ParamError.Invalid(arg, item, _)) @@ -172,17 +173,23 @@ object Router{ 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(_)) + def read(dict: Map[String, String], + default: => Option[Any], + arg: ArgSig[_, _], + thunk: String => Any): FailMaybe = { + arg.reads.arity match{ + case 0 => + tryEither(thunk(null), Result.ParamError.DefaultFailed(arg, _)).left.map(Seq(_)) + case 1 => + 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(_)) + } } + } /** @@ -214,10 +221,10 @@ object Router{ * Invoking the [[EntryPoint]] failed because the arguments provided * did not line up with the arguments expected */ - case class MismatchedArguments(missing: Seq[ArgSig[_]], + case class MismatchedArguments(missing: Seq[ArgSig[_, _]], unknown: Seq[String], - duplicate: Seq[(ArgSig[_], Seq[String])], - incomplete: Option[ArgSig[_]]) extends Error + duplicate: Seq[(ArgSig[_, _], Seq[String])], + incomplete: Option[ArgSig[_, _]]) extends Error /** * Invoking the [[EntryPoint]] failed because there were problems * deserializing/parsing individual arguments @@ -231,12 +238,12 @@ object Router{ * 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 + 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 + case class DefaultFailed(arg: ArgSig[_, _], ex: Throwable) extends ParamError } } @@ -254,14 +261,13 @@ object Router{ } } - 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 makeReadCall(dict: Map[String, String], + default: => Option[Any], + arg: ArgSig[_, _]) = { + read(dict, default, arg, arg.reads.reads(_)) } - def makeReadVarargsCall[T: scopt.Read](arg: ArgSig[_], - values: Seq[String]) = { - readVarargs[T](arg, values, implicitly[scopt.Read[T]].reads(_)) + def makeReadVarargsCall(arg: ArgSig[_, _], values: Seq[String]) = { + readVarargs(arg, values, arg.reads.reads(_)) } } @@ -357,8 +363,9 @@ class Router [C <: Context](val c: C) { case None => q"scala.None" case Some(s) => q"scala.Some($s)" } + val argSig = q""" - ammonite.main.Router.ArgSig( + mill.main.Router.ArgSig[$curCls, $docUnwrappedType]( ${arg.name.toString}, ${docUnwrappedType.toString + (if(vararg) "*" else "")}, $docTree, @@ -368,12 +375,12 @@ class Router [C <: Context](val c: C) { val reader = if(vararg) q""" - ammonite.main.Router.makeReadVarargsCall[$docUnwrappedType]( + mill.main.Router.makeReadVarargsCall( $argSig, $extrasSymbol ) """ else q""" - ammonite.main.Router.makeReadCall[$docUnwrappedType]( + mill.main.Router.makeReadCall( $argListSymbol, $default, $argSig @@ -395,8 +402,8 @@ class Router [C <: Context](val c: C) { }.unzip - q""" - ammonite.main.Router.EntryPoint[$curCls]( + val res = q""" + mill.main.Router.EntryPoint[$curCls]( ${meth.name.toString}, scala.Seq(..$argSigs), ${methodDoc match{ @@ -405,16 +412,17 @@ class Router [C <: Context](val c: C) { }}, ${varargs.contains(true)}, ($baseArgSym: $curCls, $argListSymbol: Map[String, String], $extrasSymbol: Seq[String]) => - ammonite.main.Router.validate(Seq(..$readArgs)) match{ - case ammonite.main.Router.Result.Success(List(..$argNames)) => - ammonite.main.Router.Result.Success( + mill.main.Router.validate(Seq(..$readArgs)) match{ + case mill.main.Router.Result.Success(List(..$argNames)) => + mill.main.Router.Result.Success( $baseArgSym.${meth.name.toTermName}(..$argNameCasts) ) - case x: ammonite.main.Router.Result.Error => x + case x: mill.main.Router.Result.Error => x }, ammonite.main.Router.Overrides() ) """ + res } def hasMainAnnotation(t: MethodSymbol) = { diff --git a/core/src/mill/main/RunScript.scala b/core/src/mill/main/RunScript.scala index 3aaeb44f..17d520e7 100644 --- a/core/src/mill/main/RunScript.scala +++ b/core/src/mill/main/RunScript.scala @@ -155,16 +155,14 @@ object RunScript{ // 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.define.TargetScopt.currentRootModule.set(evaluator.rootModule) - mill.eval.Evaluator.dynamicScopt.set(evaluator) + mill.main.MagicScopt.currentEvaluator.set(evaluator) mill.main.Resolve.resolve( sel.value.toList, rootModule, discover, args, crossSelectors.toList, Nil ) } finally{ - mill.eval.Evaluator.dynamicScopt.set(null) - mill.define.TargetScopt.currentRootModule.set(null) + mill.main.MagicScopt.currentEvaluator.set(null) } } EitherOps.sequence(selected) diff --git a/core/src/mill/main/Scopt.scala b/core/src/mill/main/Scopt.scala deleted file mode 100644 index 57d6529d..00000000 --- a/core/src/mill/main/Scopt.scala +++ /dev/null @@ -1,19 +0,0 @@ -//package mill.main -// -//import mill.define.{BaseModule, Discover, ExternalModule} -// -////class ModuleScopt[T <: mill.Module, M <: BaseModule](rootModule: M, d: => Discover[M]) -//// extends scopt.Read[Seq[T]]{ -//// def arity = 1 -//// def reads = s => { -//// val (expanded, Nil) = ParseArgs(Seq(s)).fold(e => throw new Exception(e), identity) -//// expanded.map{ -//// case (Some(scoping), segments) => -//// val moduleCls = rootModule.getClass.getClassLoader.loadClass(scoping.render + "$") -//// val externalRootModule = moduleCls.getField("MODULE$").get(moduleCls).asInstanceOf[ExternalModule] -//// externalRootModule.millInternal.segmentsToModules(segments).asInstanceOf[T] -//// case (None, segments) => -//// rootModule.millInternal.segmentsToModules(segments).asInstanceOf[T] -//// } -//// } -////} diff --git a/core/src/mill/main/Scripts.scala b/core/src/mill/main/Scripts.scala new file mode 100644 index 00000000..334c610f --- /dev/null +++ b/core/src/mill/main/Scripts.scala @@ -0,0 +1,330 @@ +package mill.main +import java.nio.file.NoSuchFileException + + +import mill.main.Router.{ArgSig, EntryPoint} +import ammonite.ops._ +import ammonite.runtime.Evaluator.AmmoniteExit +import ammonite.util.Name.backtickWrap +import ammonite.util.Util.CodeSource +import ammonite.util.{Name, Res, Util} +import fastparse.utils.Utils._ + +/** + * Logic around using Ammonite as a script-runner; invoking scripts via the + * macro-generated [[Router]], and pretty-printing any output or error messages + */ +object Scripts { + def groupArgs(flatArgs: List[String]): Seq[(String, Option[String])] = { + var keywordTokens = flatArgs + var scriptArgs = Vector.empty[(String, Option[String])] + + while(keywordTokens.nonEmpty) keywordTokens match{ + case List(head, next, rest@_*) if head.startsWith("-") => + scriptArgs = scriptArgs :+ (head, Some(next)) + keywordTokens = rest.toList + case List(head, rest@_*) => + scriptArgs = scriptArgs :+ (head, None) + keywordTokens = rest.toList + + } + scriptArgs + } + + def runScript(wd: Path, + path: Path, + interp: ammonite.interp.Interpreter, + scriptArgs: Seq[(String, Option[String])] = Nil) = { + interp.watch(path) + val (pkg, wrapper) = Util.pathToPackageWrapper(Seq(), path relativeTo wd) + + for{ + scriptTxt <- try Res.Success(Util.normalizeNewlines(read(path))) catch{ + case e: NoSuchFileException => Res.Failure("Script file not found: " + path) + } + + processed <- interp.processModule( + scriptTxt, + CodeSource(wrapper, pkg, Seq(Name("ammonite"), Name("$file")), Some(path)), + autoImport = true, + // Not sure why we need to wrap this in a separate `$routes` object, + // but if we don't do it for some reason the `generateRoutes` macro + // does not see the annotations on the methods of the outer-wrapper. + // It can inspect the type and its methods fine, it's just the + // `methodsymbol.annotations` ends up being empty. + extraCode = Util.normalizeNewlines( + s""" + |val $$routesOuter = this + |object $$routes + |extends scala.Function0[scala.Seq[ammonite.main.Router.EntryPoint[$$routesOuter.type]]]{ + | def apply() = ammonite.main.Router.generateRoutes[$$routesOuter.type] + |} + """.stripMargin + ), + hardcoded = true + ) + + routeClsName <- processed.blockInfo.lastOption match{ + case Some(meta) => Res.Success(meta.id.wrapperPath) + case None => Res.Skip + } + + mainCls = + interp + .evalClassloader + .loadClass(processed.blockInfo.last.id.wrapperPath + "$") + + routesCls = + interp + .evalClassloader + .loadClass(routeClsName + "$$routes$") + + scriptMains = + routesCls + .getField("MODULE$") + .get(null) + .asInstanceOf[() => Seq[Router.EntryPoint[Any]]] + .apply() + + + mainObj = mainCls.getField("MODULE$").get(null) + + res <- Util.withContextClassloader(interp.evalClassloader){ + scriptMains match { + // If there are no @main methods, there's nothing to do + case Seq() => + if (scriptArgs.isEmpty) Res.Success(()) + else { + val scriptArgString = + scriptArgs.flatMap{case (a, b) => Seq(a) ++ b}.map(literalize(_)) + .mkString(" ") + + Res.Failure("Script " + path.last + " does not take arguments: " + scriptArgString) + } + + // If there's one @main method, we run it with all args + case Seq(main) => runMainMethod(mainObj, main, scriptArgs) + + // If there are multiple @main methods, we use the first arg to decide + // which method to run, and pass the rest to that main method + case mainMethods => + val suffix = formatMainMethods(mainObj, mainMethods) + scriptArgs match{ + case Seq() => + Res.Failure( + s"Need to specify a subcommand to call when running " + path.last + suffix + ) + case Seq((head, Some(_)), tail @ _*) => + Res.Failure( + "To select a subcommand to run, you don't need --s." + Util.newLine + + s"Did you mean `${head.drop(2)}` instead of `$head`?" + ) + case Seq((head, None), tail @ _*) => + mainMethods.find(_.name == head) match{ + case None => + Res.Failure( + s"Unable to find subcommand: " + backtickWrap(head) + suffix + ) + case Some(main) => + runMainMethod(mainObj, main, tail) + } + } + } + } + } yield res + } + def formatMainMethods[T](base: T, mainMethods: Seq[Router.EntryPoint[T]]) = { + if (mainMethods.isEmpty) "" + else{ + val leftColWidth = getLeftColWidth(mainMethods.flatMap(_.argSignatures)) + + val methods = + for(main <- mainMethods) + yield formatMainMethodSignature(base, main, 2, leftColWidth) + + Util.normalizeNewlines( + s""" + | + |Available subcommands: + | + |${methods.mkString(Util.newLine)}""".stripMargin + ) + } + } + def getLeftColWidth[T](items: Seq[ArgSig[T, _]]) = { + items.map(_.name.length + 2) match{ + case Nil => 0 + case x => x.max + } + } + def formatMainMethodSignature[T](base: T, + main: Router.EntryPoint[T], + leftIndent: Int, + leftColWidth: Int) = { + // +2 for space on right of left col + val args = main.argSignatures.map(renderArg(base, _, leftColWidth + leftIndent + 2 + 2, 80)) + + val leftIndentStr = " " * leftIndent + val argStrings = + for((lhs, rhs) <- args) + yield { + val lhsPadded = lhs.padTo(leftColWidth, ' ') + val rhsPadded = rhs.lines.mkString(Util.newLine) + s"$leftIndentStr $lhsPadded $rhsPadded" + } + val mainDocSuffix = main.doc match{ + case Some(d) => Util.newLine + leftIndentStr + softWrap(d, leftIndent, 80) + case None => "" + } + + s"""$leftIndentStr${main.name}$mainDocSuffix + |${argStrings.map(_ + Util.newLine).mkString}""".stripMargin + } + def runMainMethod[T](base: T, + mainMethod: Router.EntryPoint[T], + scriptArgs: Seq[(String, Option[String])]): Res[Any] = { + val leftColWidth = getLeftColWidth(mainMethod.argSignatures) + + def expectedMsg = formatMainMethodSignature(base: T, mainMethod, 0, leftColWidth) + + def pluralize(s: String, n: Int) = { + if (n == 1) s else s + "s" + } + + mainMethod.invoke(base, scriptArgs) match{ + case Router.Result.Success(x) => Res.Success(x) + case Router.Result.Error.Exception(x: AmmoniteExit) => Res.Success(x.value) + case Router.Result.Error.Exception(x) => Res.Exception(x, "") + case Router.Result.Error.MismatchedArguments(missing, unknown, duplicate, incomplete) => + val missingStr = + if (missing.isEmpty) "" + else { + val chunks = + for (x <- missing) + yield "--" + x.name + ": " + x.typeString + + val argumentsStr = pluralize("argument", chunks.length) + s"Missing $argumentsStr: (${chunks.mkString(", ")})" + Util.newLine + } + + + val unknownStr = + if (unknown.isEmpty) "" + else { + val argumentsStr = pluralize("argument", unknown.length) + s"Unknown $argumentsStr: " + unknown.map(literalize(_)).mkString(" ") + Util.newLine + } + + val duplicateStr = + if (duplicate.isEmpty) "" + else { + val lines = + for ((sig, options) <- duplicate) + yield { + s"Duplicate arguments for (--${sig.name}: ${sig.typeString}): " + + options.map(literalize(_)).mkString(" ") + Util.newLine + } + + lines.mkString + + } + val incompleteStr = incomplete match{ + case None => "" + case Some(sig) => + s"Option (--${sig.name}: ${sig.typeString}) is missing a corresponding value" + + Util.newLine + + } + + Res.Failure( + Util.normalizeNewlines( + s"""$missingStr$unknownStr$duplicateStr$incompleteStr + |Arguments provided did not match expected signature: + | + |$expectedMsg + |""".stripMargin + ) + ) + + case Router.Result.Error.InvalidArguments(x) => + val argumentsStr = pluralize("argument", x.length) + val thingies = x.map{ + case Router.Result.ParamError.Invalid(p, v, ex) => + val literalV = literalize(v) + val rendered = {renderArgShort(p)} + s"$rendered: ${p.typeString} = $literalV failed to parse with $ex" + case Router.Result.ParamError.DefaultFailed(p, ex) => + s"${renderArgShort(p)}'s default value failed to evaluate with $ex" + } + + Res.Failure( + Util.normalizeNewlines( + s"""The following $argumentsStr failed to parse: + | + |${thingies.mkString(Util.newLine)} + | + |expected signature: + | + |$expectedMsg + """.stripMargin + ) + ) + } + } + + def softWrap(s: String, leftOffset: Int, maxWidth: Int) = { + val oneLine = s.lines.mkString(" ").split(' ') + + lazy val indent = " " * leftOffset + + val output = new StringBuilder(oneLine.head) + var currentLineWidth = oneLine.head.length + for(chunk <- oneLine.tail){ + val addedWidth = currentLineWidth + chunk.length + 1 + if (addedWidth > maxWidth){ + output.append(Util.newLine + indent) + output.append(chunk) + currentLineWidth = chunk.length + } else{ + currentLineWidth = addedWidth + output.append(' ') + output.append(chunk) + } + } + output.mkString + } + def renderArgShort[T](arg: ArgSig[T, _]) = "--" + backtickWrap(arg.name) + def renderArg[T](base: T, + arg: ArgSig[T, _], + leftOffset: Int, + wrappedWidth: Int): (String, String) = { + val suffix = arg.default match{ + case Some(f) => " (default " + f(base) + ")" + case None => "" + } + val docSuffix = arg.doc match{ + case Some(d) => ": " + d + case None => "" + } + val wrapped = softWrap( + arg.typeString + suffix + docSuffix, + leftOffset, + wrappedWidth - leftOffset + ) + (renderArgShort(arg), wrapped) + } + + + def mainMethodDetails[T](ep: EntryPoint[T]) = { + ep.argSignatures.collect{ + case ArgSig(name, tpe, Some(doc), default) => + Util.newLine + name + " // " + doc + }.mkString + } + + /** + * Additional [[scopt.Read]] instance to teach it how to read Ammonite paths + */ + implicit def pathScoptRead: scopt.Read[Path] = scopt.Read.stringRead.map(Path(_, pwd)) + +} diff --git a/core/test/src/mill/define/CacherTests.scala b/core/test/src/mill/define/CacherTests.scala index 077fea8d..606de846 100644 --- a/core/test/src/mill/define/CacherTests.scala +++ b/core/test/src/mill/define/CacherTests.scala @@ -9,7 +9,6 @@ import utest._ import utest.framework.TestPath import mill.util.TestEvaluator.implicitDisover -import TargetScopt.millScoptTargetReads object CacherTests extends TestSuite{ object Base extends Base trait Base extends TestUtil.BaseModule{ diff --git a/core/test/src/mill/eval/CrossTests.scala b/core/test/src/mill/eval/CrossTests.scala index 4e772a40..aa12e180 100644 --- a/core/test/src/mill/eval/CrossTests.scala +++ b/core/test/src/mill/eval/CrossTests.scala @@ -1,12 +1,11 @@ package mill.eval import ammonite.ops._ -import mill.define.{Discover, TargetScopt} +import mill.define.Discover import mill.util.TestEvaluator import mill.util.TestEvaluator.implicitDisover import mill.util.TestGraphs.{crossResolved, doubleCross, nestedCrosses, singleCross} import utest._ -import TargetScopt.millScoptTargetReads object CrossTests extends TestSuite{ val tests = Tests{ 'singleCross - { diff --git a/core/test/src/mill/eval/FailureTests.scala b/core/test/src/mill/eval/FailureTests.scala index 6bf53f7c..90cff686 100644 --- a/core/test/src/mill/eval/FailureTests.scala +++ b/core/test/src/mill/eval/FailureTests.scala @@ -6,7 +6,7 @@ import mill.eval.Result.OuterStack import utest._ import utest.framework.TestPath import mill.util.TestEvaluator.implicitDisover -import mill.define.TargetScopt.millScoptTargetReads + object FailureTests extends TestSuite{ val tests = Tests{ diff --git a/core/test/src/mill/eval/ModuleTests.scala b/core/test/src/mill/eval/ModuleTests.scala index 278b46cc..c6125b32 100644 --- a/core/test/src/mill/eval/ModuleTests.scala +++ b/core/test/src/mill/eval/ModuleTests.scala @@ -6,7 +6,6 @@ import mill.T import mill.define.Discover import mill.util.TestEvaluator.implicitDisover import utest._ -import mill.define.TargetScopt.millScoptTargetReads object ModuleTests extends TestSuite{ object ExternalModule extends mill.define.ExternalModule { diff --git a/core/test/src/mill/eval/TaskTests.scala b/core/test/src/mill/eval/TaskTests.scala index ea82677d..114a2910 100644 --- a/core/test/src/mill/eval/TaskTests.scala +++ b/core/test/src/mill/eval/TaskTests.scala @@ -5,7 +5,6 @@ import ammonite.ops._ import mill.T import mill.util.TestEvaluator.implicitDisover import mill.util.TestEvaluator -import mill.define.TargetScopt.millScoptTargetReads object TaskTests extends TestSuite{ val tests = Tests{ object build extends mill.util.TestUtil.BaseModule{ diff --git a/core/test/src/mill/main/MainTests.scala b/core/test/src/mill/main/MainTests.scala index e1a419cb..22f93ae0 100644 --- a/core/test/src/mill/main/MainTests.scala +++ b/core/test/src/mill/main/MainTests.scala @@ -4,7 +4,6 @@ import mill.define.{Discover, Segment, Task} import mill.util.TestGraphs._ import mill.util.TestEvaluator.implicitDisover import utest._ -import mill.define.TargetScopt.millScoptTargetReads object MainTests extends TestSuite{ def check[T <: mill.Module](module: T)( diff --git a/core/test/src/mill/util/TestUtil.scala b/core/test/src/mill/util/TestUtil.scala index 9a5baf11..7ef43943 100644 --- a/core/test/src/mill/util/TestUtil.scala +++ b/core/test/src/mill/util/TestUtil.scala @@ -1,6 +1,6 @@ package mill.util -import ammonite.main.Router.Overrides +import mill.main.Router.Overrides import ammonite.ops.pwd import mill.define._ import mill.eval.Result diff --git a/scalalib/src/mill/scalalib/GenIdea.scala b/scalalib/src/mill/scalalib/GenIdea.scala index 2f76b666..4b283e7c 100644 --- a/scalalib/src/mill/scalalib/GenIdea.scala +++ b/scalalib/src/mill/scalalib/GenIdea.scala @@ -12,15 +12,23 @@ import mill.util.Strict.Agg object GenIdeaModule extends ExternalModule { - def idea() = T.command{ mill.scalalib.GenIdea() } + + def idea(ev: Evaluator[Any]) = T.command{ + mill.scalalib.GenIdea( + implicitly, + ev.rootModule, + ev.discover + ) + } + + implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() def millDiscover = Discover[this.type] } object GenIdea { - def apply()(implicit ctx: Log, - rootModule0: BaseModule.Implicit, - discover: Discover[_]): Unit = { - val rootModule = rootModule0.value + def apply(ctx: Log, + rootModule: BaseModule, + discover: Discover[_]): Unit = { val pp = new scala.xml.PrettyPrinter(999, 4) rm! pwd/".idea" rm! pwd/".idea_modules" diff --git a/scalalib/src/mill/scalalib/PublishModule.scala b/scalalib/src/mill/scalalib/PublishModule.scala index 993fe7e6..e7f36609 100644 --- a/scalalib/src/mill/scalalib/PublishModule.scala +++ b/scalalib/src/mill/scalalib/PublishModule.scala @@ -2,7 +2,7 @@ package mill package scalalib import ammonite.ops._ -import mill.define.{ExternalModule, TargetScopt, Task} +import mill.define.{ExternalModule, Task} import mill.eval.{PathRef, Result} import mill.scalalib.publish.{Artifact, SonatypePublisher} import mill.util.Loose.Agg @@ -88,7 +88,7 @@ trait PublishModule extends ScalaModule { outer => object PublishModule extends ExternalModule{ def publishAll(sonatypeCreds: String, gpgPassphrase: String, - publishArtifacts: TargetScopt.Targets[(mill.scalalib.publish.Artifact, Seq[(PathRef, String)])], + publishArtifacts: mill.main.MagicScopt.Tasks[(mill.scalalib.publish.Artifact, Seq[(PathRef, String)])], sonatypeUri: String = "https://oss.sonatype.org/service/local", sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots") = T.command{ val x: Seq[(Seq[(Path, String)], Artifact)] = Task.sequence(publishArtifacts.items)().map{ @@ -104,6 +104,7 @@ object PublishModule extends ExternalModule{ x:_* ) } + implicit def millScoptTargetReads[T] = new mill.main.MagicScopt[T]() def millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type] }
\ No newline at end of file |