Skip to content

Added a simple version of a polling loop.#1140

Merged
3 commits merged intoocaml:masterfrom
kodek16:watch-mode
Sep 5, 2018
Merged

Added a simple version of a polling loop.#1140
3 commits merged intoocaml:masterfrom
kodek16:watch-mode

Conversation

@kodek16
Copy link
Copy Markdown
Contributor

@kodek16 kodek16 commented Aug 15, 2018

dune build -w now behaves in a similar way to Jenga polling mode,
automatically rebuilding the project when changes are detected.
dune runtest -w is similar, but also runs tests after successful
build.

Note that on platforms other than Linux, you need to have fswatch
installed for watch mode to work.

bin/main.ml Outdated
targets)

let fswatch_command root_path =
let excludes = [ {|/\.#|}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few elements in this list only seem relevant when not doing out-of-source builds. Let's restrict it to:

                  [ {|\.#|}
                  ; {|_build|}
                  ; {|\.hg|}
                  ; {|\.git|}
                  ; {|~$|}
                  ; {|/#[^#]*#$|}
                  ; {|\.install$|}
                  ]

bin/main.ml Outdated
let log = Log.create common in
Scheduler.go ~log ~common
(Main.setup ~log common >>= fun setup ->

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The polling mode is useful for both dune build and dune runtest. Could you factorize the code so that it works with both commands?

bin/main.ml Outdated
>>= fun () ->
fswatch_command File_tree.(Dir.path (root setup.file_tree))
>>= fun (fswatch, args) ->
let fswatch =
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do this outside of the loop. Dir.path (root setup.file_tree) is always Path.root

@ghost
Copy link
Copy Markdown

ghost commented Aug 15, 2018

One thing I just remember we need to handle is promotion. Sometimes, dune records files that needs to be promoted to the source tree. When --auto-promote is passed, these files should be promoted at the end of the build and we should loop immediately. The Promotion module records the files that need to be promoted

@Khady
Copy link
Copy Markdown
Contributor

Khady commented Aug 15, 2018 via email

@kodek16
Copy link
Copy Markdown
Contributor Author

kodek16 commented Aug 15, 2018

@Khady, file changes while build is active are not detected yet, but I agree this is definitely something to be improved in near future.

@kodek16
Copy link
Copy Markdown
Contributor Author

kodek16 commented Aug 15, 2018

@diml, auto-rebuilds after promotions will be solved in a natural way once build restart on changes is implemented. For now I've added a simple hackish flag to retrigger build if at least one file was promoted.

@Khady
Copy link
Copy Markdown
Contributor

Khady commented Aug 16, 2018

Might be interesting to look at https://github.com/watchexec/watchexec then. It is multi platform, and it handles interruptions.

Anyway, thanks for working on this feature!

bin/main.ml Outdated
in
let path = Path.to_string_maybe_quoted root_path in
let uname =
match Bin.which "uname" with
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than doing this in two steps (and relying on uname), can we first check if inotifywait exists and use it if it does?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

bin/main.ml Outdated
Format.eprintf "Error: executable not found: %s\n" fswatch;
exit 1
in
Process.run_capture Strict fswatch args ~env:Env.initial
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this is intended, but it seems that all call sites of watch_changes pipe it into ignore, so it might be worth returning unit directly instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even: Process.run ~stdout:Config.dev_null

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added >>| ignore for now, but in the future we're likely to need the actual names of files that were modified, so this is going to change anyway.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the time being let's use Process.run ~stdout:Config.dev_null. It's likely that in the future we'll need to keep the process alive and read its output continously, so we'll need something else than Process.run.

bin/main.ml Outdated
>>= fun _ ->
main_loop ()
in
try
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simplify this code with a single loop: let rec loop f = try Scheduler.go ... (f ()) ...

bin/main.ml Outdated
let targets = resolve_targets_exn ~log common setup targets in
do_build setup targets
in
if common.watch then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code looks very similar to the one for dune runtest. It can be factorized a bit more

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@ghost
Copy link
Copy Markdown

ghost commented Aug 23, 2018

I tried it again and it seems to work well when combined with --auto-promote. However, the messages Success, polling ... are now gone.

@kodek16 kodek16 requested a review from rgrinberg as a code owner August 24, 2018 09:26
@ghost
Copy link
Copy Markdown

ghost commented Aug 29, 2018

Never mind, I tried again and it works. I was using dune exec -- dune runtest -w. This must be a bug in dune exec

@kodek16
Copy link
Copy Markdown
Contributor Author

kodek16 commented Aug 29, 2018

One other thing that we should consider is the naming: do you think we should stick with -w for "watch mode", or switch to -p for polling mode as in Jenga?

bin/main.ml Outdated
fswatch, ["-r"; path; "-1"] @ excludes
| None ->
(* Exit immediately to prevent a loop of these errors. *)
Format.eprintf "Error: fswatch not found. \
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To match the style of other errors, could you use:

die "@{<error>Error@}: fswatch ..."

The message needs to mention inotify as well since we look for either of these tools


open! Stdune

type status_line_config = string option * [`Show_jobs | `Don't_show_jobs]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type seems simpler to me:

type status_line_config =
  { message : string option
  ; show_jobs : bool
  }

src/scheduler.ml Outdated
Format.eprintf "Internal error: setup failed.\n";
exit 1
in
let init =
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be init (). In general we assume that a given Fiber.t value is executed only once

@ghost
Copy link
Copy Markdown

ghost commented Aug 29, 2018

Could you try adding a module Hooks with the following signature:

(** Register a function called at the end of every build *)
val at_end_of_build : (unit -> unit) -> unit

(** Regsiter a function called at the end of the current built only *)
val at_end_of_current_build : (unit -> unit) -> unit

(** Invoke all registered hooks *)
val end_of_build : (unit -> unit)

and use it to deal with the various cleanup functions? I feel like this would simplify a bit the code

@ghost
Copy link
Copy Markdown

ghost commented Aug 29, 2018

One other thing that we should consider is the naming: do you think we should stick with -w for "watch mode", or switch to -p for polling mode as in Jenga?

Unfortunately, we are already using -p for release builds. I.e. the build command packages must use when built with opam is: dune build -p <package>.

@kodek16
Copy link
Copy Markdown
Contributor Author

kodek16 commented Aug 31, 2018

@diml I tried added the module, since it's a much better idea than at_exit anyway, but I feel really reluctant about introducing more global state, especially if we consider interrupting builds in the future, which would make it not trivial to distinguish between leftover state (potentially including hooks) from old builds.

I would stick with passing Build_system.t to finally, which would do the clean-up for the specific build it's called with.

bin/main.ml Outdated
(match Bin.which "fswatch" with
| Some fswatch ->
let excludes =
List.(map ~f:(fun x -> ["--exclude"; x]) excludes |> concat)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use List.concat_map here.

bin/main.ml Outdated
if common.watch then
Scheduler.poll ~cache_init:false ~log ~common ~init ~once ~finally ()
else begin
(try
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit (and preferably the entire file) needs to be ocp-indented

src/scheduler.ml Outdated
| Some c -> c
| None ->
Format.eprintf "Internal error: setup failed.\n";
exit 1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not raise a code_error here?

bin/main.ml Outdated
One of them needs to be installed for watch mode to work.\n")

let watch_changes () =
let watch, args = watch_command Path.root in
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should cache the result of this call with a lazy

bin/main.ml Outdated
| Ok targets ->
targets)

let clear_all_caches () =
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we put these in hooks?

: ?log:Log.t
-> ?config:Config.t
-> ?cache_init:bool
-> init:(unit -> 'a Fiber.t)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can now change this to unit -> unit Fiber.t, which should simplify a bit the implementation. It also seems to me that once and finally should have type ... -> unit Fiber.t, given that we currently do nothing with 'b or 'c.

@ghost
Copy link
Copy Markdown

ghost commented Sep 3, 2018

@kodek16, it's possible that we'll need to provide more granularity in hooks. For instance to allows to recognize the end of the a build vs an interruption of the current build. For long running builds, it is also likely that we will need to add a mechanism to dump the databases at regular intervals. The hooks interface allows to keep all the logic related to a system in its own module, which is nice.

BTW, We should put End_of_build.run in an at_exit, to make sure it's always called.

@emillon
Copy link
Copy Markdown
Collaborator

emillon commented Sep 3, 2018

I tried this and it looks like it will be very useful! I have some remarks:

  • when fswatch and inotifywait are not installed, this outputs errors in an infinite loop, taking 100% CPU.
  • when running dune build -w, after fixing an error, the only feedback that the error is fixed is the "Success" message that is on the same line as the "polling" message. May I suggest clearing the terminal (using Bin.which "clear" if available), or at least displaying a message on its own line? The same happens if we go from a success state to a success state: there's little feedback that the build has completed.
  • "polling filesystem" in the message makes it look like we're actively polling the filesystem rather than subscribed to events (some watch tools have a fallback mode like that). What do you think about a more neutral "waiting for filesystem changes"?
  • can you rebase this on top of master? the history is a bit messy at the moment.

And once again thanks for working on this! ✨

commit ca3c1b7
Merge: f1744b6 610b20d
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Tue Sep 4 10:28:04 2018 -0400

    Merge branch 'master' into watch-mode

commit f1744b6
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 31 16:13:53 2018 -0400

    Add Hooks module.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 09e0de6
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 31 15:08:28 2018 -0400

    Fix review suggestions.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit a59d70d
Merge: c7aa8ed 6da1f19
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 31 14:35:50 2018 -0400

    Merge branch 'master' into watch-mode

commit c7aa8ed
Merge: aa15c6c 6dedc68
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 31 09:37:28 2018 -0400

    Merge branch 'master' into watch-mode

commit aa15c6c
Merge: ce562e4 265adc8
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Wed Aug 29 11:04:25 2018 -0400

    Merge branch 'master' into watch-mode

commit ce562e4
Merge: e8b55b4 c0eebce
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Mon Aug 27 15:05:51 2018 -0400

    Merge branch 'master' into watch-mode

commit e8b55b4
Merge: 1a1289f 9d61c59
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Mon Aug 27 11:31:54 2018 -0400

    Merge branch 'master' into watch-mode

commit 1a1289f
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 24 10:40:19 2018 +0100

    Fixed build after merge.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit c2288e0
Merge: d5fd1c4 b7da030
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 24 10:26:27 2018 +0100

    Merge branch 'master' into watch-mode

commit d5fd1c4
Merge: d69b6c5 b424ba9
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Tue Aug 21 11:38:07 2018 +0100

    Merge branch 'master' into watch-mode

commit d69b6c5
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Tue Aug 21 11:26:21 2018 +0100

    Moved polling logic inside Scheduler module.

    This prevents us from calling Scheduler.go multiple times
    which may be problematic because of scheduler state.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit f76d3d1
Merge: e4935fb 5b6496f
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Tue Aug 21 09:45:58 2018 +0100

    Merge branch 'master' into watch-mode

commit e4935fb
Merge: 584b9e0 6045568
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Mon Aug 20 17:05:14 2018 +0100

    Merge branch 'master' into watch-mode

commit 584b9e0
Merge: 6740ec8 99d82c2
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Mon Aug 20 09:51:10 2018 +0100

    Merge branch 'master' into watch-mode

commit 6740ec8
Merge: a3cbc56 309dd5f
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 17 15:21:22 2018 +0100

    Merge branch 'master' into watch-mode

commit a3cbc56
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 17 14:59:19 2018 +0100

    Fixed non-polling build.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 882c5d5
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Fri Aug 17 12:59:13 2018 +0100

    Refactored polling to only call finalize once.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 3e4f17a
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Thu Aug 16 15:27:51 2018 +0100

    Made status line nicer and fixed auto-promote polling.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit f84cda3
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Thu Aug 16 14:59:50 2018 +0100

    Updated tests, removed duplicate finalize call.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 88a1373
Merge: 8f05ef5 166dd06
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Thu Aug 16 14:31:04 2018 +0100

    Merge branch 'master' into watch-mode

commit 8f05ef5
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Thu Aug 16 14:27:43 2018 +0100

    Fixed promotion behavior.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit b01f603
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Thu Aug 16 10:23:38 2018 +0100

    Refactored.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 40f63cb
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Thu Aug 16 09:51:16 2018 +0100

    Removed call to uname.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit ac2aaa7
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Wed Aug 15 16:47:03 2018 +0100

    Made polling mode auto-rebuild after promotions.

    This is a temporary solution until automatic build restarts
    are implemented.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 55fc699
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Wed Aug 15 16:10:53 2018 +0100

    Refactored polling to be more abstract.

    Polling now has better scheduler API, which allows to use
    it in `dune runtest` without duplicating too much code.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit 1146d42
Author: Jeremie Dimino <jeremie@dimino.org>
Date:   Wed Aug 15 13:44:45 2018 +0100

    Refactor a bit fswatch_command

    Avoid computing both commands eagerly.

    Signed-off-by: Jeremie Dimino <jeremie@dimino.org>

commit c046b95
Merge: fe7968d 9804526
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Wed Aug 15 11:03:09 2018 +0100

    Merge branch 'master' into watch-mode

commit fe7968d
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Wed Aug 15 10:59:27 2018 +0100

    Fixed compilation error.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

commit b6612b1
Author: Pavel Senchanka <pavel.senchanka@gmail.com>
Date:   Wed Aug 15 10:17:04 2018 +0100

    Added a simple version of a polling loop.

    `dune build -w` now behaves in a similar way to Jenga polling mode,
    automatically rebuilding the project when changes are detected.

    Note that on platforms other than Linux, you need to have `fswatch`
    installed for watch mode to work.

    Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>

Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>
Signed-off-by: Pavel Senchanka <pavel.senchanka@gmail.com>
@kodek16
Copy link
Copy Markdown
Contributor Author

kodek16 commented Sep 4, 2018

I've squashed the previous commits (it's nice that Github kept the review history reasonably clean, I wasn't sure it can do this), and fixed all of the suggestions above. @emillon Adding a newline to the message seems to preserve previous lines of Success/Failure, does it look better now?

Copy link
Copy Markdown
Collaborator

@emillon emillon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new feedback on build seems good to me. If you can amend the commit message so that it's only the "Add a simple version of the polling loop" it'd be perfect otherwise the concatenated messages are probably good enough 👍

@rgrinberg
Copy link
Copy Markdown
Member

@kodek16 give this another rebase and we'll merge after CI.

@ghost ghost merged commit 8f2a4c0 into ocaml:master Sep 5, 2018
@ghost
Copy link
Copy Markdown

ghost commented Sep 5, 2018

I did "Update branch" followed by "Squash and merge"

@kodek16 kodek16 deleted the watch-mode branch September 5, 2018 13:57
rgrinberg added a commit to rgrinberg/opam-repository that referenced this pull request Sep 14, 2018
CHANGES:

- Ignore stderr output when trying to find out the number of jobs
  available (ocaml/dune#1118, fix ocaml/dune#1116, @diml)

- Fix error message when the source directory of `copy_files` does not exist.
  (ocaml/dune#1120, fix ocaml/dune#1099, @emillon)

- Highlight error locations in error messages (ocaml/dune#1121, @emillon)

- Display actual stanza when package is ambiguous (ocaml/dune#1126, fix ocaml/dune#1123, @emillon)

- Add `dune unstable-fmt` to format `dune` files. The interface and syntax are
  still subject to change, so use with caution. (ocaml/dune#1130, fix ocaml/dune#940, @emillon)

- Improve error message for `dune utop` without a library name (ocaml/dune#1154, fix
  ocaml/dune#1149, @emillon)

- Fix parsing `ocamllex` stanza in jbuild files (ocaml/dune#1150, @rgrinberg)

- Highlight multi-line errors (ocaml/dune#1131, @anuragsoni)

- Do no try to generate shared libraries when this is not supported by
  the OS (ocaml/dune#1165, fix ocaml/dune#1051, @diml)

- Fix `Flags.write_{sexp,lines}` in configurator by avoiding the use of
  `Stdune.Path` (ocaml/dune#1175, fix ocaml/dune#1161, @rgrinberg)

- Add support for `findlib.dynload`: when linking an executable using
  `findlib.dynload`, automatically record linked in libraries and
  findlib predicates (ocaml/dune#1172, @bobot)

- Add support for promoting a selected list of files (ocaml/dune#1192, @diml)

- Add an emacs mode providing helpers to promote correction files
  (ocaml/dune#1192, @diml)

- Improve message suggesting to remove parentheses (ocaml/dune#1196, fix ocaml/dune#1173, @emillon)

- Add `(wrapped (transition "..message.."))` as an option that will generate
  wrapped modules but keep unwrapped modules with a deprecation message to
  preserve compatibility. (ocaml/dune#1188, fix ocaml/dune#985, @rgrinberg)

- Fix the flags passed to the ppx rewriter when using `staged_pps` (ocaml/dune#1218, @diml)

- Add `(env var)` to add a dependency to an environment variable.
  (ocaml/dune#1186, @emillon)

- Add a simple version of a polling mode: `dune build -w` keeps
  running and restarts the build when something change on the
  filesystem (ocaml/dune#1140, @kodek16)

- Cleanup the way we detect the library search path. We no longer call
  `opam config var lib` in the default build context (ocaml/dune#1226, @diml)

- Make test stanzas honor the -p flag. (ocaml/dune#1236, fix ocaml/dune#1231, @emillon)

- Test stanzas take an optional (action) field to customize how they run (ocaml/dune#1248,
  ocaml/dune#1195, @emillon)

- Add support for private modules via the `private_modules` field (ocaml/dune#1241, fix
  ocaml/dune#427, @rgrinberg)

- Add support for passing arguments to the OCaml compiler via a
  response file when the list of arguments is too long (ocaml/dune#1256, @diml)

- Do not print diffs by default when running inside dune (ocaml/dune#1260, @diml)

- Interpret `$ dune build dir` as building the default alias in `dir`. (ocaml/dune#1259,
  @rgrinberg)

- Make the `dynlink` library available without findlib installed (ocaml/dune#1270, fix
  ocaml/dune#1264, @rgrinberg)
This pull request was closed.
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 this pull request may close these issues.

5 participants