Skip to content

Commit

Permalink
Backported support for a target separator (+) in the argument parser
Browse files Browse the repository at this point in the history
This adds support to specify arbitrary targets and target arguments in one mill run.

__Motivation:__ Current mill only supports the selection of one or multiple targets followed by an argument list which is applied to all targets. Providing different arguments is currently not supported.

__Solution:__ We introduce a new separator `+` which separates the target arguments from the next target. To pass the `+` as argument to a target, masking with `\+` is supported.

Example:

```bash
mill __.compile + core.testCached + itest.test fastTest slowTests + __.publishLocal
```

This command will:
* run `compile` for all modules
* run `testCached` for module `core`
* run `itest.test` command with arguments ` fastTest` and `slowTests`
* run `publishLocal` for all modules

The limitation, that a command can only run once is still present and not changed by this PR.

See pull request: com-lihaoyi#1521
  • Loading branch information
lefou committed Nov 12, 2021
1 parent 09f5a5d commit 7d0dc52
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 92 deletions.
5 changes: 3 additions & 2 deletions contrib/scoverage/src/ScoverageReport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import mill.contrib.scoverage.api.ScoverageReportWorkerApi.ReportType
import mill.define.{Command, Module, Task}
import mill.eval.Evaluator
import mill.main.RunScript
import mill.util.SelectMode
import mill.{PathRef, T}
import os.Path

Expand Down Expand Up @@ -78,7 +79,7 @@ trait ScoverageReport extends Module {
mill.main.ResolveTasks,
evaluator,
Seq(sources),
multiSelect = false
SelectMode.Single
) match {
case Left(err) => throw new Exception(err)
case Right(tasks) => tasks.asInstanceOf[Seq[Task[Seq[PathRef]]]]
Expand All @@ -87,7 +88,7 @@ trait ScoverageReport extends Module {
mill.main.ResolveTasks,
evaluator,
Seq(dataTargets),
multiSelect = false
SelectMode.Single
) match {
case Left(err) => throw new Exception(err)
case Right(tasks) => tasks.asInstanceOf[Seq[Task[PathRef]]]
Expand Down
32 changes: 18 additions & 14 deletions main/core/src/eval/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ammonite.runtime.SpecialClassLoader
import mainargs.MainData
import scala.util.DynamicVariable

import mill.api.{BuildProblemReporter, DummyTestReporter, Strict, TestReporter}
import mill.api.{BuildProblemReporter, DummyTestReporter, TestReporter}
import mill.api.Result.{Aborted, OuterStack, Success}
import mill.api.Strict.Agg
import mill.define.{Ctx => _, _}
Expand Down Expand Up @@ -107,13 +107,13 @@ case class Evaluator(
// Increment the counter message by 1 to go from 1/10 to 10/10 instead of 0/10 to 9/10
val counterMsg = (i+1) + "/" + sortedGroups.keyCount
val Evaluated(newResults, newEvaluated, cached) = evaluateGroupCached(
terminal,
group,
results,
counterMsg,
reporter,
testReporter,
contextLogger
terminal = terminal,
group = group,
results = results,
counterMsg = counterMsg,
zincProblemReporter = reporter,
testReporter = testReporter,
logger = contextLogger
)
someTaskFailed = someTaskFailed || newResults.exists(task => !task._2.isInstanceOf[Success[_]])

Expand All @@ -127,11 +127,11 @@ case class Evaluator(

Evaluator.writeTimings(timings.toSeq, outPath)
Evaluator.Results(
goals.indexed.map(results(_).map(_._1)),
evaluated,
transitive,
getFailing(sortedGroups, results),
results.map{case (k, v) => (k, v.map(_._1))}
rawValues = goals.indexed.map(results(_).map(_._1)),
evaluated = evaluated,
transitive = transitive,
failing = getFailing(sortedGroups, results),
results = results.map { case (k, v) => (k, v.map(_._1)) }
)
}

Expand Down Expand Up @@ -689,7 +689,11 @@ object Evaluator{
(sortedGroups, transitive)
}

case class Evaluated(newResults: collection.Map[Task[_], Result[(Any, Int)]], newEvaluated: Seq[Task[_]], cached: Boolean)
case class Evaluated(
newResults: collection.Map[Task[_], mill.api.Result[(Any, Int)]],
newEvaluated: Seq[Task[_]],
cached: Boolean
)

// Increment the counter message by 1 to go from 1/10 to 10/10
class NextCounterMsg(taskCount: Int) {
Expand Down
73 changes: 69 additions & 4 deletions main/core/src/util/ParseArgs.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,77 @@
package mill.util

import fastparse._, NoWhitespace._
import scala.annotation.tailrec

import fastparse._
import NoWhitespace._
import mill.define.{Segment, Segments}

sealed trait SelectMode
object SelectMode {

/** All args are treated as targets or commands. If a `--` is detected, subsequent args are parameters to all commands. */
object Multi extends SelectMode

/** Only the first arg is treated as target or command, subsequent args are parameters of the command. */
object Single extends SelectMode

/** Like a combination of [[Single]] and [[Multi]], behaving like [[Single]] but using a special separator (`++`) to start parsing another target/command. */
object Separated extends SelectMode
}

object ParseArgs {

def apply(scriptArgs: Seq[String],
multiSelect: Boolean): Either[String, (List[(Option[Segments], Segments)], Seq[String])] = {
type TargetsWithParams = (Seq[(Option[Segments], Segments)], Seq[String])

/** Separator used in multiSelect-mode to separate targets from their args. */
val MultiArgsSeparator = "--"

/** Separator used in [[SelectMode.Separated]] mode to separate a target-args-tuple from the next target. */
val TargetSeparator = "+"

@deprecated("Use apply(Seq[String], SelectMode) instead", "mill after 0.10.0-M3")
def apply(
scriptArgs: Seq[String],
multiSelect: Boolean
): Either[String, TargetsWithParams] = extractAndValidate(scriptArgs, multiSelect)

def apply(
scriptArgs: Seq[String],
selectMode: SelectMode
): Either[String, Seq[TargetsWithParams]] = {

val MaskPattern = ("""\\+\Q""" + TargetSeparator + """\E""").r

/**
* Partition the arguments in groups using a separator.
* To also use the separator as argument, masking it with a backslash (`\`) is supported.
*/
@tailrec
def separated(result: Seq[Seq[String]], rest: Seq[String]): Seq[Seq[String]] = rest match {
case Seq() => result
case r =>
val (next, r2) = r.span(_ != TargetSeparator)
separated(
result ++ Seq(next.map {
case x @ MaskPattern(_*) => x.drop(1)
case x => x
}),
r2.drop(1)
)
}
val parts: Seq[Seq[String]] = separated(Seq(), scriptArgs)
val parsed: Seq[Either[String, TargetsWithParams]] =
parts.map(extractAndValidate(_, selectMode == SelectMode.Multi))

val res1: Either[String, Seq[TargetsWithParams]] = EitherOps.sequence(parsed)

res1
}

private def extractAndValidate(
scriptArgs: Seq[String],
multiSelect: Boolean
): Either[String, TargetsWithParams] = {
val (selectors, args) = extractSelsAndArgs(scriptArgs, multiSelect)
for {
_ <- validateSelectors(selectors)
Expand All @@ -21,7 +86,7 @@ object ParseArgs {
multiSelect: Boolean): (Seq[String], Seq[String]) = {

if (multiSelect) {
val dd = scriptArgs.indexOf("--")
val dd = scriptArgs.indexOf(MultiArgsSeparator)
val selectors = if (dd == -1) scriptArgs else scriptArgs.take(dd)
val args = if (dd == -1) Seq.empty else scriptArgs.drop(dd + 1)

Expand Down
61 changes: 46 additions & 15 deletions main/src/main/MainModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,57 @@ import mill.T
import mill.define.{NamedTask, Task}
import mill.api.{PathRef, Result}
import mill.eval.Evaluator
import mill.util.{Ctx, PrintLogger, Watched}
import mill.util.{Ctx, PrintLogger, SelectMode, Watched}
import pprint.{Renderer, Truncated}

object MainModule{
def resolveTasks[T](evaluator: Evaluator, targets: Seq[String], multiSelect: Boolean)
(f: List[NamedTask[Any]] => T) = {
RunScript.resolveTasks(mill.main.ResolveTasks, evaluator, targets, multiSelect) match{
@deprecated(
"use resolveTasks(Evaluator, Seq[String], SelectMode) instead",
"mill after 0.9.9"
)
def resolveTasks[T](
evaluator: Evaluator,
targets: Seq[String],
multiSelect: Boolean
)(f: List[NamedTask[Any]] => T): Result[T] =
resolveTasks(evaluator, targets, if (multiSelect) SelectMode.Multi else SelectMode.Single)(f)

def resolveTasks[T](
evaluator: Evaluator,
targets: Seq[String],
selectMode: SelectMode
)(f: List[NamedTask[Any]] => T): Result[T] = {
RunScript.resolveTasks(mill.main.ResolveTasks, evaluator, targets, selectMode) match {
case Left(err) => Result.Failure(err)
case Right(tasks) => Result.Success(f(tasks))
}
}
def evaluateTasks[T](evaluator: Evaluator, targets: Seq[String], multiSelect: Boolean)
(f: Seq[(Any, Option[ujson.Value])] => T) = {
RunScript.evaluateTasks(evaluator, targets, multiSelect) match{

@deprecated(
"use evaluateTasks(Evaluator, Seq[String], SelectMode) instead",
"mill after 0.9.9"
)
def evaluateTasks[T](
evaluator: Evaluator,
targets: Seq[String],
multiSelect: Boolean
)(f: Seq[(Any, Option[ujson.Value])] => T): Result[Watched[Unit]] =
evaluateTasks(evaluator, targets, if (multiSelect) SelectMode.Multi else SelectMode.Single)(f)

def evaluateTasks[T](
evaluator: Evaluator,
targets: Seq[String],
selectMode: SelectMode
)(f: Seq[(Any, Option[ujson.Value])] => T): Result[Watched[Unit]] = {
RunScript.evaluateTasks(evaluator, targets, selectMode) match {
case Left(err) => Result.Failure(err)
case Right((watched, Left(err))) => Result.Failure(err, Some(Watched((), watched)))
case Right((watched, Right(res))) =>
f(res)
Result.Success(Watched((), watched))
}
}

}

trait MainModule extends mill.Module{
Expand All @@ -51,7 +81,7 @@ trait MainModule extends mill.Module{
*/
def resolve(evaluator: Evaluator, targets: String*) = mill.T.command{
val resolved = RunScript.resolveTasks(
mill.main.ResolveMetadata, evaluator, targets, multiSelect = true
mill.main.ResolveMetadata, evaluator, targets, SelectMode.Multi
)

resolved match{
Expand Down Expand Up @@ -81,7 +111,7 @@ trait MainModule extends mill.Module{

private def plan0(evaluator: Evaluator, targets: Seq[String]) = {
RunScript.resolveTasks(
mill.main.ResolveTasks, evaluator, targets, multiSelect = true
mill.main.ResolveTasks, evaluator, targets, SelectMode.Multi
) match {
case Left(err) => Left(err)
case Right(rs) =>
Expand All @@ -98,7 +128,7 @@ trait MainModule extends mill.Module{
*/
def path(evaluator: Evaluator, src: String, dest: String) = mill.T.command{
val resolved = RunScript.resolveTasks(
mill.main.ResolveTasks, evaluator, List(src, dest), multiSelect = true
mill.main.ResolveTasks, evaluator, List(src, dest), SelectMode.Multi
)

resolved match{
Expand Down Expand Up @@ -138,7 +168,7 @@ trait MainModule extends mill.Module{
* Displays metadata about the given task without actually running it.
*/
def inspect(evaluator: Evaluator, targets: String*) = mill.T.command{
MainModule.resolveTasks(evaluator, targets, multiSelect = true){ tasks =>
MainModule.resolveTasks(evaluator, targets, SelectMode.Multi){ tasks =>
val output = new StringBuilder
for{
task <- tasks
Expand All @@ -165,11 +195,12 @@ trait MainModule extends mill.Module{
* Runs multiple tasks in a single call.
* For compatibility reasons, the tasks are executed single-threaded.
*/
@deprecated("Use `+` separator instead", "mill after 0.9.9")
def all(evaluator: Evaluator, targets: String*) = mill.T.command {
MainModule.evaluateTasks(
evaluator = if (evaluator.effectiveThreadCount > 1) evaluator.copy(threadCount = Some(1)) else evaluator,
targets = targets,
multiSelect = true) { res =>
SelectMode.Multi) { res =>
res.flatMap(_._2)
}
}
Expand All @@ -181,7 +212,7 @@ trait MainModule extends mill.Module{
MainModule.evaluateTasks(
evaluator = evaluator,
targets = targets,
multiSelect = true) { res =>
SelectMode.Multi) { res =>
res.flatMap(_._2)
}
}
Expand All @@ -201,7 +232,7 @@ trait MainModule extends mill.Module{
}
),
targets,
multiSelect = false
SelectMode.Multi
) {res =>
for(json <- res.flatMap(_._2)){
println(json.render(indent = 4))
Expand Down Expand Up @@ -287,7 +318,7 @@ trait MainModule extends mill.Module{
}

RunScript.resolveTasks(
mill.main.ResolveTasks, evaluator, targets, multiSelect = true
mill.main.ResolveTasks, evaluator, targets, SelectMode.Multi
) match {
case Left(err) => Result.Failure(err)
case Right(rs) => planTasks match {
Expand Down
3 changes: 2 additions & 1 deletion main/src/main/MainScopts.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mill.main

import mill.eval.Evaluator
import mill.util.SelectMode

case class Tasks[T](value: Seq[mill.define.NamedTask[T]])

Expand All @@ -12,7 +13,7 @@ object Tasks{
mill.main.ResolveTasks,
Evaluator.currentEvaluator.get,
s,
multiSelect = false
selectMode.Single
).map(x => Tasks(x.asInstanceOf[Seq[mill.define.NamedTask[T]]])),
alwaysRepeatable = false,
allowEmpty = false
Expand Down
2 changes: 1 addition & 1 deletion main/src/main/Resolve.scala
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ object ResolveTasks extends Resolve[NamedTask[Any]]{

// Contents of `either` *must* be a `Task`, because we only select
// methods returning `Task` in the discovery process
case Some(either) => either.right.map(Seq(_))
case Some(either) => either.map(Seq(_))
}
}
}
Expand Down
Loading

0 comments on commit 7d0dc52

Please sign in to comment.