17

How can I force build.rs to run again without cleaning my whole project? I checked cargo build --help but I couldn't find anything related to build.rs.

dtolnay
  • 9,621
  • 5
  • 41
  • 62
tversteeg
  • 4,717
  • 10
  • 42
  • 77

6 Answers6

19

If you print

"cargo:rerun-if-changed=<FILE>"

the build will be triggered every time the file has changed.

rerun-if-changed=PATH is a path to a file or directory which indicates that the build script should be re-run if it changes (detected by a more-recent last-modified timestamp on the file). Normally build scripts are re-run if any file inside the crate root changes, but this can be used to scope changes to just a small set of files. -- source

I'm not aware of a solution without changing a file manually (I just put a whitespace anywhere in my build.rs, it will be removed by rustfmt though).

I have several buildscripts in my projects, and mostly these two lines give me a nice solution:

println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=path/to/Cargo.lock");

but I guess you are looking for a command rustc/cargo command. Anyway, you can put in a small script, which will edit a certain file, which will trigger the build-process.

Tim Diekmann
  • 7,755
  • 11
  • 41
  • 69
  • 2
    I don't understand "Note that if the build script itself (or one of its dependencies) changes, then it's rebuilt and rerun unconditionally, so cargo:rerun-if-changed=build.rs is almost always redundant (unless you want to ignore changes in all other files except for build.rs)." this is not needed – Stargateur Apr 05 '19 at 13:10
  • 1
    It should not be needed, however, I noticed that sometimes it does not work (maybe this was a bug, which has been solved now?). – Tim Diekmann Apr 05 '19 at 13:44
16

Register build.rs as a crate's bin target:

  1. Add this to your Cargo.toml file:
[package]
edition = "2018"
build = "build.rs"

[[bin]]
name = "force-build"
path = "build.rs"
required-features = ["build_deps"]  # only needed for build-dependencies
  1. If you have any [build-dependencies] (e.g. some_crate = "1.2.3"), you need to add those to (the main) [dependencies] (sadly no [bin-dependencies] as of yet), but you can make them optional:
[dependencies]
some_crate = { version = "1.2.3", optional = true }

[features]
build_deps = ["some_crate"]

Then you can run the build script with:

$ cargo run --bin force-build --features build_deps

(or $ cargo run --bin force-build when no [build-dependencies])

  • You can even disable the automatic call of the build script by replacing the build = "build.rs" line in Cargo.toml with build = false

  • Note: since the OUT_DIR env var is not present for bin targets, if your build.rs script uses env!("OUT_DIR"), you may "fix this" by using concat!(env!("CARGO_MANIFEST_DIR"), "/target/") instead.

Caesar
  • 6,733
  • 4
  • 38
  • 44
Daniel H-M
  • 1,439
  • 8
  • 12
  • 3
    thanks this was a super helpful departure from the typical solutions – maxm Aug 24 '19 at 18:43
  • this works but i'm getting a warning "warning: file `.../build.rs` found to be present in multiple build targets". any idea how to avoid it? – Markus Unterwaditzer Mar 29 '23 at 17:41
  • @MarkusUnterwaditzer Yeah, this is exactly what this solution intends to do, but it's not really how Cargo would expect it, I guess, hence the warning. Another approach could be to create a `src/bin/force_build.rs` file, with `include!("../../build.rs");` as its body. The advantage of this other approach is that you won't even need to touch the `Cargo.toml` file unless you needed the `build_deps` features shenanigans – Daniel H-M Mar 30 '23 at 19:54
10

If build.rs changes, Cargo already rebuilds the project:

Note that if the build script itself (or one of its dependencies) changes, then it's rebuilt and rerun unconditionally, so cargo:rerun-if-changed=build.rs is almost always redundant (unless you want to ignore changes in all other files except for build.rs). doc

On Linux, I will just do touch build.rs && cargo build. For Windows, see Windows equivalent of the Linux command 'touch'?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Stargateur
  • 24,473
  • 8
  • 65
  • 91
5

If you got target under gitignore (which you should) this might be useful for any file changes when you're developing and testing the build script.

if Path::new(".git/HEAD").exists() {
    println!("cargo:rerun-if-changed=.git/HEAD");
}
Seivan
  • 668
  • 6
  • 13
2

if you're trying to rebuild based on non-rust or include!() files that might've changed, you can use

const _: &[u8] = include_bytes!("foobar.baz");

to ensure that any changes to those files will trigger a new build. pretty sure this solution adds neither time nor filesize.

you can shove this into a macro too, so its easy to do a bunch of files.

macro_rules! build_on{($file:literal) => {
    const _: &[u8] = include_bytes!($file);
}

build_on!("foobar.baz");
Bad Radish
  • 111
  • 1
  • 2
0

I'm currently on stable rust 1.70.0, and what I accidentally realized is that if you use a non-existent file path with cargo:rerun-if-changed, it will always trigger the build script.

So simply add it to the top of your build script like this:

// build.rs
fn main() {
    println!("cargo:rerun-if-changed=NULL");
    // do your build stuff here...
}

I'm not sure if this creates any side effects or other unintended issues. So far, it works perfectly fine for me. Remember, this could easily change in the future and your build scripts could fail without you even knowing.

m4heshd
  • 773
  • 9
  • 23