watchexec / cargo-watch

Watches over your Cargo project's source.

Home Page:https://watchexec.github.io/#cargo-watch

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature Suggestion: conditional run

LeDominik opened this issue · comments

I really like the practice of keeping cargo check running continuously in the background thanks to this awesome tool. But recently I really wish the following would be possible: Do something like cargo watch -x check -x run with a twist:

  1. If a file changes run check without terminating the application (in my case a rocket application)
  2. If check succeeds, then terminate the application and do the run...

This would be really nice, as it would only restart the server if it's actually safe to do so. Hence I can keep hacking around and trying the current (running) version safely until the code compiles again. Maybe that's something that only seems useful to me, but I really believe this would be super-handy...

Because we delegate almost entirely to watchexec, this would have to be possible there before it can be implemented here.

But! This is actually possible with a bit of playing around and two cargo-watch instances. The idea is that one runs the server, and the other runs -x check -s "a command that restarts the server". To let cargo-watch take care of that restart, you can have the server one watch only a specific file/folder with -w, and then the second command of the check one modifies that file, triggering a restart. Perhaps:

$ cargo watch -w .trigger -x run

and

$ cargo watch -x check -s 'touch .trigger'

I love this quick approach!! Didn’t come to my mind...

I'll put a note of it on the Readme, but then close this because direct support is an upstream issue.

Also: would you be ok if I posted this on the /r/rust subreddit? I think it's a useful pattern

hey, @passcod; sure -- it's really your pattern 👍
So my aim would be for it to work as follows...

  1. Add .watch-trigger to your .gitignore (thus cargo watch will not pick up the trigger-file by default)
  2. Launch cargo watch -x check -s 'touch .watch-trigger' in your first check-Shell, let it run until the first check succeeds leading to the initial creation of the .watch-trigger file
  3. Launch cargo watch --no-gitignore -w .watch-trigger -x run in your second run-Shell where your web-application will run

Update: This works!
However, this doesn't work (yet) as cargo watch in the second shell seems to be ignoring the explicitly set trigger file... possibly due to .gitignore? So right now I just do it with the watch-file in a different directory (/tmp)

Have you tried with --no-gitignore in the second shell?

Aha, that does the trick... Updated my response up there. I would have thought that the explicit -w flag would always overrule .gitignore file...

It doesn't, because you can watch e.g. a folder within your source but still want to have the ignores apply. Also because it makes things simpler, both to code and to understand, when options only do one thing and don't switch the entire mode of the application without warning.

It does make it a bit odd in this particular case, though. I'll think on that.

@passcod Clever solution but how could it be done on Windows? Windows doesn't have touch.

And btw, if I have a workspace with a lib and a bin crate, where the bin depends on the lib, how can I use cargo watch to restart the bin when the lib changes?

I'd say just use redirection to write to the file. touch is useful on nix, but any kind of modification Notify picks up works.

And for the lib/bin, same solution, but given #52 is not implemented yet, you might have to use -s commands to target the right thing instead of just -x commands.

How can I target the binary subcrate with run?

Even with a normal cargo run --bin app I get:

error: manifest is a virtual manifest

Whatever command or commands you usually have to just run the binary subcrate, use those. -s is freeform. -s "cd subcrate; cargo run" or something?

It doesn't work:

D:\projects\foo-workspace>cargo watch -x check -s "echo > .watch"
[Running 'cargo check
 Downloading arrayvec v0.3.22
[Running 'cargo check
 Downloading arrayvec v0.3.22
[Running 'cargo check
 Downloading arrayvec v0.3.22
[Running 'cargo check
 Downloading arrayvec v0.3.22
[Running 'cargo check
 Downloading arrayvec v0.3.22
[Running 'cargo check
 Downloading arrayvec v0.3.22
[Running 'cargo check
...

Btw, it creates a file named .watch'], why?

How can I only watch Rust source files? Or exclude the target dir?

It seems the generated command is not parsed correctly on Windows. Windows CMD is very... particular. So what it tries to run it echo [Running 'cargo check && echo > .watch'] && cargo check && echo > .watch. Bash and most other shells interpret that correctly. CMD somehow ignores the quotes and echoes [Running 'cargo check then runs the first echo > .watch'], which touches .watch'], which is not ignored, thus triggering a rerun.

That's a different issue not related to this thread, though. If you pass -q, it should suppress the [Running] messages and circumvent this issue. (I'm reluctant to try to fix the problem because I don't have a Windows CMD handy and don't know its rules enough to do it blind. I'll accept a PR, of course.)

Also, these last questions are covered in the help, and target is already excluded by default.

cmd.exe only respects double quotes ", but why does it matter, isn't the command ran through the native API via a lib function?

Why doesn't it run echo "[Running 'cargo check && echo > .watch']" && cargo check && echo > .watch (with double quotes around the string to be echoed)?

To prevent the lag when building stuff, I used (on an actix project):

$ cargo watch -i .trigger -x build -s 'touch .trigger' &
$ systemfd --no-pid -s http::3000 -- cargo watch -w .trigger -x run

Is there a reason not to just eliminate the double process and execute cargo run in the success block like so: cargo watch build -s 'cargo run'?

This seems to work for me.

Your command is equivalent to

cargo watch -x build -x run

The problem in this ticket was that if cargo build fails, it's killed the run. And also, it's killed the run during the build. If you want e.g. a server to keep running while you fix compile errors, that's not great.

Ah that makes sense, I just didn't happen to interact with my server while it was rebuilding.

Great solution from @passcod here, but... Is there any plans to develop some native options to do this in a single-line command?

As a matter of fact, yes! What shape this will take is still nebulous, but I hope to have it for the end of the year.

The core capability (running more than one process within an instance) will land upstream next month, and then the 'frontend' work will take a bit more time.

In my team we used this approach to tackle both the issue of stopping processes before the changes pass cargo check, as well as the workspace crate separation issue.

Imagine we have multiple binary and library crates as part of a workspace, as shown below in a minimal example:

.
├── Cargo.toml
├── lib1
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── service1
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── service2
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── shared_lib
    ├── Cargo.toml
    └── src
        └── lib.rs

We run multiple workspace services at the same time, but we want to only recompile and restart a service when either its own code or the code of one of the workspace libraries it depends on changes and passes cargo check.

To achieve this, we will need 2x + y processes running, where x is the number of binaries in the workspace to run and y the number of library crates to monitor for changes. This is made way easier with some pre-canned multi-process handling application like mprocs, so that's what we use.

mprocs configuration for the simple workspace shown above looks like this:

procs:
    shared_lib-check:
        cmd: [cargo, watch, -w, "shared_lib", -x, "check -p shared_lib", -s, "touch .trigger-shared_lib"]
    lib1-check:
        cmd: [cargo, watch, -w, "lib1", -x, "check -p lib1", -s, "touch .trigger-lib1"]
    service1-check:
        cmd: [cargo, watch, -w, "service1", -x, "check -p service1", -s, "touch .trigger-service1"]
    service2-check:
        cmd: [cargo, watch, -w, "service2", -x, "check -p service2", -s, "touch .trigger-service2"]
    service1-run:
        cmd: [cargo, watch,
         -w, ".trigger-shared_lib",
         -w, ".trigger-lib1",
         -w, ".trigger-service1",
         -x, "run -p service1"]
    service2-run:
        cmd: [cargo, watch,
         -w, ".trigger-shared_lib",
         -w, ".trigger-service2",
         -x, "run -p service2"]

This will start 4 cargo check processes to monitor the two libs and the 2 services, as well as 2 cargo run processes to serve the binaries. Changes to the shared lib will trigger a rebuild of both services, while changes to lib1 will only trigger a rebuild of service1.

PS: There is no need to use --no-vcs-ignores as suggested in the README, since this approach doesn't monitor the workspace root.