Skip to content
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

--watch does not work for T.input #838

Closed
lefou opened this issue Apr 29, 2020 · 2 comments · Fixed by #2489
Closed

--watch does not work for T.input #838

lefou opened this issue Apr 29, 2020 · 2 comments · Fixed by #2489
Milestone

Comments

@lefou
Copy link
Member

lefou commented Apr 29, 2020

See also related issue #832 and it's fix 2b3fb05.

@lefou
Copy link
Member Author

lefou commented Apr 29, 2020

Created a PR #839 which tackles the issue for most common types: PathRef, Seq[PathRef], os.Path and Seq[os.Path].

@lefou
Copy link
Member Author

lefou commented Apr 30, 2020

A possible solution looks like this (copied from #839 (comment)): We want to plumb the lambda that T.input captures and change the rest of the RunScript infrastructure that does watching/etc. to know how to handle either PathRefs or lambdas, instead of being hard-coded to PathRefs as it is now. We could even re-use the ammonite.interp.Watchable type, since that models exactly this and is used for exactly the same use case

lihaoyi added a commit that referenced this issue May 4, 2023
Fixes #838 and also fixes
#909

We add the ability to re-run `T.input{...}` blocks during the polling
loop, just like we re-stat the `os.Paths` reported by `T.source` and
`T.sources`. This involves some complexity, since `T.input{...}` blocks
are not just a single lambda but instead a wrapper for a part of the
`Task` graph that feeds into it. We capture the body of `evaluateGroup`
in a lambda so we can call it later

# Major Changes

1. Modify `Evaluator.Results#results` from a `Map[Task, Result]` to
`Map[Task, TaskResult]`, where `TaskResult[T]` is a wrapper of
`Result[T]` that also contains a `recalcOpt: Option[() => Result[T]]`
callback

2. Wrap the bulk of `Evaluator#evaluateGroup` in a function, that we
evaluate once to populate `TaskResult#result` and store in a callback to
populate `TaskResult#recalcOpt`

3. Update `RunScript.evaluateNamed` to handle `InputImpl`s, in addition
to the current handling of `SourceImpl`s and `SourcesImpl`s, and
translate the `recalcOpt` callback into a `Watchable.Value`

4. The `Watchable.Value` is then propagated to
`MillBuildBootstrap.evaluateWithWatches`, which ends up feeding it into
`Watching.scala` where it is processed the same way as the
`Watchable.Value`s we get from `interp.watchValue`

5. Overhauled the implementation of `show`; rather than returning a
`Watched` wrapper, we now call back to `interp.evalWatch0` to register
the things we need to watch. I also consolidated the common code in
`show` and `showNamed` into one shared helper.

6. I wrapped most of the `Any`s or `_`s in `Evaluator` with a new `case
class Val(value: Any)` wrapper. This should considerably increase type
safety: e.g. you can no longer pass an `Option[Val]` where a `Val` was
expected, unless you manually wrap it in `Val(...)` which is hard to do
accidentally. This makes it much easier to ensure the various data
structures inside `Evaluator` are passed around correctly and not
mis-used

# Minor Changes

1. Cleaned up `RunScripts` considerably, now there's only two methods
left and no copy-pasty code

2. Removed some `T.input`s from `MillBuildRootModule`: `def
unmanagedClasspath` can be a `T.sources`, and `def parseBuildFiles` is
now a normal target that depends on `allBuildFiles` which is a
`T.sources`. That should improve the `--watch` status message in the
terminal and reduce the amount of work that needs to be done every 100ms
while polling for changes

# Testing 

Tested manually with the following `build.sc`; verified with `-w bar`
that `bar` re-runs every 2s, and the script re-evaluates every 10s

```scala
import mill._

interp.watchValue(System.currentTimeMillis() / 10000)

println("Setting up build.sc")
def foo = T.input{ System.currentTimeMillis() / 2000 }

def bar = T{ foo() + " seconds since the epoch" }
```

Also added `integration/feature/watch-source-input/` suite that starts
up a `--watch` in a background thread, makes changes to the files on
disk, and ensures the `build.sc` and tasks within it re-evaluate as
expected.

# Notes

1. I'm not sure if the new way `show` registers watches is better than
the old one. The old way with `Watched` can be made to work even in
failure cases if we pass it as the optional second parameter to
`Result.Failure`

2. The `Val` change is not strictly necessary, and is pretty invasive.
But it was very hard to make the necessary changes to `Evaluator`
without it due to bugs passing the wrong `Any`s around. So I think it's
a reasonable time to introduce `Val` to support future work inside
`Evaluator` with increased compiler support and type safety

3. The `recalc` logic would be a lot simpler if `input`s couldn't have
upstream tasks, since we could just re-run the block rather than
re-evaluating the task group. I had some thoughts about restricting
`T.input`s to allow this, but couldn't convince myself that was the
right thing to do, so left the `T.input` semantics in place and just
made it work by capturing a callback that can evaluate the entire task
group
@lefou lefou added this to the 0.11.0-M9 milestone May 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant