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

Feature Suggestion: conditional run #75

Closed
LeDominik opened this issue Aug 12, 2017 · 24 comments
Closed

Feature Suggestion: conditional run #75

LeDominik opened this issue Aug 12, 2017 · 24 comments

Comments

@LeDominik
Copy link

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...

@passcod
Copy link
Member

passcod commented Aug 12, 2017

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'

@LeDominik
Copy link
Author

LeDominik commented Aug 12, 2017

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

@passcod
Copy link
Member

passcod commented Aug 12, 2017

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

@passcod
Copy link
Member

passcod commented Aug 12, 2017

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

@LeDominik
Copy link
Author

LeDominik commented Aug 13, 2017

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)

@passcod
Copy link
Member

passcod commented Aug 14, 2017

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

@LeDominik
Copy link
Author

LeDominik commented Aug 14, 2017

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

@passcod
Copy link
Member

passcod commented Aug 14, 2017

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.

@Boscop
Copy link

Boscop commented Aug 17, 2017

@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?

@passcod
Copy link
Member

passcod commented Aug 17, 2017

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.

@Boscop
Copy link

Boscop commented Aug 17, 2017

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

@passcod
Copy link
Member

passcod commented Aug 17, 2017

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?

@Boscop
Copy link

Boscop commented Aug 17, 2017

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?

@passcod
Copy link
Member

passcod commented Aug 17, 2017

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.

@Boscop
Copy link

Boscop commented Aug 17, 2017

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

@passcod
Copy link
Member

passcod commented Aug 17, 2017

No.

@Boscop
Copy link

Boscop commented Aug 19, 2017

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

@pickfire
Copy link

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

@quodlibetor
Copy link

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.

@passcod
Copy link
Member

passcod commented Feb 23, 2023

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.

@quodlibetor
Copy link

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

@PabloGmz96
Copy link

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

@passcod
Copy link
Member

passcod commented Jun 19, 2023

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.

@joaommartins
Copy link

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.

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

No branches or pull requests

7 participants