-
-
Notifications
You must be signed in to change notification settings - Fork 360
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
JavaModule's run
command arguments should be a Task
#1948
Comments
I tried a prototype in #2452. Thinking about this, it appears there are a few options here:
It feels like either (3) or (4) are the best options here, depending on how much we want to generalize this v.s. how difficult it is to make the upstream |
Beside what this means for the codebase, I think what we want is a
The If we look at a more complex use case like def ivyDepsTree(args: IvyDepsTreeArgs = IvyDepsTreeArgs()): Command[Unit] with IvyDepsTreeArgs, which is annotated with various |
com-lihaoyi/mainargs#62 has the mainargs necessary to make (3) work. It would be a binary compatibility breakage for Mainargs, but IMO it's probably worth doing sooner or later, as apart from supporting this feature it would also clean up a lot of cruft within the MainArgs codebase, and if we're breaking bin-compat in Mill 0.11.0 then it could be worth doing now |
I've got #2452 updated to use the mainargs PR above ( There's also some concern about how to receive the output of the |
…port alternate implementations (#62) This PR moves the handling of `mainargs.Leftover`/`Flag`/`SubParser` from a hard-coded `ArgSig` that only works with `mainargs.Leftover` or `mainargs.Flag`, to properties of the `TokensReader` that can be configured to work with any custom type. Should probably be reviewed concurrently with com-lihaoyi/mill#1948, which is the motivation for this PR: we want to be able to define a CLI entrypoing taking `mill.define.Task[mainargs.Leftover[T]]` or equivalent, which is currently impossible due to the hard-coded nature of `mainargs.Leftover` (and `mainargs.Flag` etc.) # Major Changes 1. `ArgReader` is eliminated and `ArgSig` is greatly simplified to a single type with no subtypes or type parameters 2. `TokensReader` is split into 5primary sub-types - `.Simple`, `Constant`, `.Flag`, `.Leftover`, and `.Class`. These roughly mirror the original `{ArgSig,ArgReader}.{Simple,Flag,Leftover,Class}` case classes. The 5 sub-classes control behavior through `Renderer.scala`/`Invoker.scala`/`TokensGrouping.scala` in the same way. The major effect of moving the logic from `{ArgSig,ArgReader}` to `TokensReader` is that they now are no longer hard-coded to work with `mainargs.{Flag,Leftover,Subparser}` types. Now, anyone who has a custom type `Foo` can choose whether they want to define a `TokensReader.Simple` for it, or whether they want to define a `TokensReader.Leftover` or `TokensReader.Flag`. Similarly, people can define their own `TokensReader.Class` rather than relying on the default implementation in `mainargs.ParserForClass`. # Testing Tested with two new flavors of `VarargsTests` (now renamed `VarargsBasedTests`: 1. `VarargsWrappedTests` that exercises using a custom wrapper type to define a main entrypoints that takes `Wrapper[mainargs.Leftover[T]]`, 2. `VarargsCustomTests` that replaces `mainargs.Leftover[T]` entirely and defines main entrypoints that take `Wrapper[T]` 3. Added a `ConstantTests.scala` to exercise the code path, which was previously the `noTokens` codepath and un-tested in this repo 4. All existing tests pass # Notes 1. I chose to remove the type params from `ArgSig` because they weren't really paying for their complexity; most of the time we were passing around `ArgSig[_, _]`s anyway, so we weren't getting type safety, but they nevertheless caused tons of headaches trying to get types to line up. The un-typed ` default: Option[Any => Any], reader: TokensReader[_]` isn't great, but it's a lot easier to work with and TBH not really much less type-safe than the status quo 2. Because `ArgSig` and `TokensReader` now have a circular dependency on each other (via `TokensReader.Class` -> `MainData` -> `ArgSig` -> `TokensReader`), I moved them into the same file. This both makes the acyclic linter happy, and also kind of makes sense since they're now part of the same recursive data structure (v.s. previously `ArgSig` was the recursive data structure with `TokensReader`s hanging off of each node) 3. The new structure with `TokensReader` as a `sealed trait` with 5 distinct sub-types is different from what it was before, with `TokensReader` as a single `class` with a grab-bag of all possible fields and callbacks. I thought the `sealed trait` approach is much cleaner here, since they reflect exactly the data necessary 4 different scenarios we care about, whereas otherwise we'd find some fields meaningless in some cases e.g. `Flag` has no meaningful fields, `Leftover` doesn't care about `noTokens` or `alwaysRepeatable` or `allowEmpty`, etc.
Fixes #1948. Should probably be reviewed concurrently with com-lihaoyi/mainargs#62 which it depends on # Major Changes 1. Pull in com-lihaoyi/mainargs#62, which allows MainArg's "aggregate leftover arguments" logic to apply to custom types and not be hardcoded to `mainargs.Leftover` 2. Update MainArgs integration code in this repo to accomadate the changes 3. Replace the `def run(args: String*)` with `def run(args: Task[Args] = T.task(Args()))`. # Minor Changes 1. I moved the `import mill.main.TokenReaders._` into the `Discover` macro, so we can stop manually importing it everywhere. 2. I moved all the aliases from `import mill.define._` to `import mill._`. `Task[_]` in particular is a type I believe we should start encouraging people to use more often - e.g. annotating all command parameter types as `Task[_]`s - and so it should be in the default `import mill._` without needing a separate import. 3. Added a direct dependency to `com.lihaoyi::pprint`, since the implicit dependency in `mainargs` was removed in 0.5.0 due to being unnecessary # Testing 1. All existing tests pass, at least those I've run locally so far (`./mill -i -j 3 example.__.local.test`, can't use CI until the mainargs PR is published). This includes unit tests that call `run` programmatically and integration/example tests that call `run` via the CLI. 4. Added a new example test `5-module-run-task`, that demonstrates and explains how to use a `ScalaModule`'s `run` method to implement a `task`, passing in input parameters from upstream tasks and using the output (in this case generated files) in downstream tasks # Notes 1. There is still some boilerplate around `run`, both defining it `args: Task[Args] = T.task(Args())` and calling it programmatically, `run(T.task(Args(...)))`. For now I haven't figured out how to reduce this, as we already "used up" our implicit conversion budget with the `T.*{...}` macros. Probably can be done, although it won't be quite as low-boilerplate as the original `args: String*` syntax. Maybe in a follow-up PR 2. The example test `5-module-run-task` is something I've wanted to be able to write for years now. Nice to finally be able to do it without having to piece together a `Jvm.runSubprocess()` call myself! 3. `5-module-run-task` is still a bit clunkier than I was hoping. Having to `def barWorkingDir` beforehand to pass it to both the `bar.run` and the `def sources` override is weird and unintuitive. Maybe we can change the `def run` return type to make it easier to return the `T.dest` folder or other metadata out of the body of `def run` so it can be used by downstream tasks? e.g. making `def run` return `Command[PathRef]` rather than `Command[Unit]` 5. IMO we should recommend that all CLI arguments to `T.command`s come in the form of `Task[T]`s, unless there's concrete reasons otherwise (e.g. we want to dynamically change `T.command` implementations based on the input). That would maximise the ability to re-use commands in the body of of tasks, which is something people probably want --------- Co-authored-by: Tobias Roeser <[email protected]>
Currently, we can't define a
def a: Task[String] = ???
and use that to call therun
command from theJavaModule
. See the minimal repo:As per a conversation on
gitter
with @lefou , a straightforward fix for that is to turndef run(args: String*): Command[Unit]
intodef run(args: Task[Seq[String]]): Command[Unit]
The text was updated successfully, but these errors were encountered: