-
-
Notifications
You must be signed in to change notification settings - Fork 369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Task.Command(exclusive = true)
and convert Task.Persistent
to Task(persistent = true)
#3617
Conversation
I'd prefer to explicitly denote commands that are not parallel capable, e.g. Even in older Mill versions, we run all commands in parallel if the |
It would be nice, if we could avoid the introduction of yet another target factory. Maybe, we could make it configurable via a parameter, e.g. |
@lefou I think |
Either that or the one with the dot notation. I think the flag-version is nicer (more idiomatic Scala), but if we provide a version without flags for conciseness accepting a parameter supposed to implicitly converted to |
If we're doing this, we should review all target factories like |
Task.ParallelCommand
that runs in parallel, use it to make TestModule#test
run in parallelTask.Command(serial = true)
and convert Task.Persistent
to Task(persistent = true)
Task.Command(serial = true)
and convert Task.Persistent
to Task(persistent = true)
Task.Command(exclusive = true)
and convert Task.Persistent
to Task(persistent = true)
@lefou I've updated the PR, the implementation to make it work is a bit hacky but seems to work and compiles/passes-tests |
I think In theory we may find it useful to have |
I'd like to hear what @lolgab thinks about this new notation. |
/** | ||
* Dummy class used to mark parameters that come after it as named only parameters | ||
*/ | ||
object NamedParameterOnlyDummy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps a SIP idea: an annotation that forces callsites to use named arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or some new syntax at the declaration anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good idea!
t: NamedParameterOnlyDummy.type = NamedParameterOnlyDummy, | ||
exclusive: Boolean = false | ||
): CommandFactory = new CommandFactory(exclusive) | ||
class CommandFactory private[mill] (val exclusive: Boolean) extends TaskBase.TraverseCtxHolder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why shouldn't this be a curried function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC Scala 2 macros had an issue where they didnt allow you to call their parameters with explicit names. Actually I'm not sure if that still applies in 2.13.15 or 3.5.0, but it did at some point. Lemme try again to verify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
works in scala 3.5 anyway:
scala> def mcr[T: Type](fn: Expr[Either[String, T]], caller: Expr[String], exclusive: Expr[Boolean])(using Quotes): Expr[Either[String, T]] = {
| import scala.concurrent.Future
| import scala.concurrent.ExecutionContext.Implicits.global
| import scala.concurrent.Await
| import scala.concurrent.duration.Duration
|
| val exc = exclusive.valueOrAbort
| if exc then '{ println($caller); println("EXCLUSIVE"); $fn }
| else '{ println($caller); val fut = Future({Thread.sleep(1000); $fn}); Await.result(fut, Duration.Inf) }
| }
scala> {
| inline def task[T](inline op: Either[String, T]) = ${ mcr[T]('op, '{"original"}, 'false) }
| inline def task[T](ignore: Int = -1, inline exclusive: Boolean = false)(inline op: Either[String, T]) = ${ mcr[T]('op, '{"overload"}, 'exclusive) }
| }
scala> task(exclusive = true) { println(1 + 1); Right(23) }
overload
EXCLUSIVE
2
scala> task(exclusive = false) { println(1 + 1); Right(23) }
overload
2
scala> task{ println(1 + 1); Right(23) }
original
2
val res21: Either[String, Int] = Right(23)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah looks like it's still a limitation in 2.13.15
[14186] [error] /Users/lihaoyi/Github/mill/main/src/mill/main/MainModule.scala:403:17: macro applications do not support named and/or default arguments
[14186] [error] Task.Command(exclusive = true) {
[14186] [error] ^
So likely we'll go with the boilerplate-y CommandFactory
so this can land in 0.12.0, and in 0.13.0 we can convert to normal curried functions as one of the benefits of being on Scala 3.x. Doesn't need to be immediately, since the CommandFactory
approach should work in Scala 3 as well I think
if (tasksTransitive.contains(t)) true | ||
else t match { | ||
case t: Command[_] => !t.exclusive | ||
case _ => false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lihaoyi Shouldn't this be a true
or a !serialCommandExec
?
Maybe, serialCommandExec
isn't need anymore, since we now have fine control over exclusive execution.
fixes #3566. Mostly a straightforward implementation of what was discussed.
We use
Task.Command(exclusive = true)
to makeconsole
/repl
/clean
/etc. run serially, all other commands should run in parallel. That includes mosttest
commands. The nameexclusive
is taken from the Bazel action/target tag with the same meaning: that the tagged action runs alone with no other actions in parallelTask.Persistent
was changed toTask(persistent = true)
for consistency, and also for other reasons: the new syntax is more composable, e.g. a user can more easily choose whether they wantpersistent = true
orpersistent = false
based on a computedBoolean
value, and Mill can in future extend it such that we can haveTask(exclusive = true, persistent = true)
,Task.Command(exclusive = true, persistent = true)
, or other such combinations perhaps with even more flags (e.g. Bazel has a pretty long list https://bazel.build/reference/be/common-definitions#common.tags).To make overload resolution work correctly, I make the first parameter of the
(exclusive = true)
or(persistent = true)
parameter listdummy: NamedParameterOnlyDummy.type = NamedParameterOnlyDummy
. This ensures that there is no normal value the user could pass in positionally that would select that overload ofdef Command
ordef apply
, and the only way to select it is by passing inexclusive = true
orpersistent = true
as a named parameterTested manually by adding
println
s and making sure thatleafSerialCommands
no longer contains test tasks when I run tests inexample/scalalib/basic/1-simple
.