From 494014ea37c2bfd201219e145cdfc31408c21b03 Mon Sep 17 00:00:00 2001 From: Ikeh Chukwuka Date: Sat, 7 Nov 2026 07:10:05 +0100 Subject: [PATCH] cpp_build: emit rerun-if-changed for every parsed source file Signed-off-by: Ikeh Chukwuka --- cpp/src/lib.rs | 11 +++++++++++ cpp_build/src/lib.rs | 27 +++++++++++++++++++++----- cpp_build/src/parser.rs | 43 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index dff6586..9afbcf6 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -70,6 +70,17 @@ //! //! Behind the scene, it uses the `cc` crate. //! +//! `cpp_build` tells cargo to rerun the build script when one of the Rust source files it +//! parses changes. It doesn't track C++ headers included from `cpp!` macros, or files added +//! with `cpp_build::Config::file`, so print `cargo:rerun-if-changed` for those yourself: +//! +//! ```no_run +//! fn main() { +//! cpp_build::build("src/lib.rs"); +//! println!("cargo:rerun-if-changed=src/header.h"); +//! } +//! ``` +//! //! ## Using external libraries //! //! Most likely you will want to link against external libraries. You need to tell cpp_build diff --git a/cpp_build/src/lib.rs b/cpp_build/src/lib.rs index 023d768..4c523ad 100644 --- a/cpp_build/src/lib.rs +++ b/cpp_build/src/lib.rs @@ -399,7 +399,8 @@ Failed to create output object directory."#, /// `cc::Build` object. pub struct Config { cc: cc::Build, - std_flag_set: bool, // true if the -std flag was specified + std_flag_set: bool, // true if the -std flag was specified + cargo_metadata: bool, // false if cargo metadata output was disabled } impl Default for Config { @@ -411,10 +412,14 @@ impl Default for Config { /// Convert from a preconfigured [`cc::Build`] object to [`Config`]. /// Note that this will ensure C++ is enabled and add the proper include /// directories to work with `cpp`. +/// +/// If the [`cc::Build`] has `cargo_metadata(false)`, also call +/// [`Config::cargo_metadata`] with `false`. cpp_build can't read that setting +/// from `cc`, so otherwise it still prints `cargo:rerun-if-changed` lines. impl From for Config { fn from(mut cc: cc::Build) -> Self { cc.cpp(true).include(cargo_manifest_dir()); - Self { cc, std_flag_set: false } + Self { cc, std_flag_set: false, cargo_metadata: true } } } @@ -593,9 +598,10 @@ impl Config { } /// Define whether metadata should be emitted for cargo allowing it to - /// automatically link the binary. Defaults to `true`. + /// automatically link the binary, and to rerun the build script when a + /// parsed Rust source file changes. Defaults to `true`. pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Self { - // XXX: Use this to control the cargo metadata which rust-cpp produces + self.cargo_metadata = cargo_metadata; self.cc.cargo_metadata(cargo_metadata); self } @@ -639,7 +645,18 @@ impl Config { // Parse the crate let mut visitor = parser::Parser::default(); - if let Err(err) = visitor.parse_crate(crate_root.as_ref().to_owned()) { + let result = visitor.parse_crate(crate_root.as_ref().to_owned()); + + // `cc` prints `rerun-if-env-changed` lines, and any `rerun-if` line stops Cargo from + // rerunning the build script when a file in the package changes. So tell Cargo about + // every parsed file, otherwise new or changed `cpp!` macros are not extracted. + if self.cargo_metadata { + for path in &visitor.source_files { + println!("cargo:rerun-if-changed={}", path.display()); + } + } + + if let Err(err) = result { warnln!( r#"-- rust-cpp parse error -- There was an error parsing the crate for the rust-cpp build script: diff --git a/cpp_build/src/parser.rs b/cpp_build/src/parser.rs index d36a26c..2d06def 100644 --- a/cpp_build/src/parser.rs +++ b/cpp_build/src/parser.rs @@ -296,6 +296,45 @@ fn test_cursor_advance() -> Result<(), LexError> { Ok(()) } +#[test] +fn test_source_files() { + // A crate with every module layout that the parser follows, and a module that is disabled + // by a feature, whose file doesn't exist. + let lib = r#" + mod adjacent; + mod dir; + mod inline { + mod nested; + } + #[path = "renamed.rs"] + mod with_path; + #[cfg(feature = "cpp_build_test_disabled")] + mod disabled; + "#; + let files = [ + ("lib.rs", lib), + ("adjacent.rs", "mod child;"), + ("adjacent/child.rs", ""), + ("dir/mod.rs", ""), + ("inline/nested.rs", ""), + ("renamed.rs", ""), + ]; + let root = std::env::temp_dir().join(format!("cpp_build_source_files_{}", std::process::id())); + for (file, source) in files { + let path = root.join(file); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(path, source).unwrap(); + } + + let mut parser = Parser::default(); + let result = parser.parse_crate(root.join("lib.rs")); + std::fs::remove_dir_all(&root).unwrap(); + result.unwrap(); + + let expected: Vec = files.iter().map(|(file, _)| root.join(file)).collect(); + assert_eq!(parser.source_files, expected); +} + fn line_directive(path: &Path, cur: Cursor) -> String { let mut line = format!("#line {} \"{}\"\n", cur.line + 1, path.to_string_lossy().replace('\\', "\\\\")); @@ -311,7 +350,8 @@ pub struct Parser { pub classes: Vec, pub snippets: String, pub callbacks_count: u32, - current_path: PathBuf, // The current file being parsed + pub source_files: Vec, // Every file that was read while parsing the crate + current_path: PathBuf, // The current file being parsed mod_dir: PathBuf, mod_error: Option, // An error occuring while visiting the modules } @@ -323,6 +363,7 @@ impl Parser { } fn parse_mod(&mut self, mod_path: PathBuf, submod_dir: PathBuf) -> Result<(), Error> { + self.source_files.push(mod_path.clone()); let mut s = String::new(); let mut f = File::open(&mod_path).map_err(|_| Error::ParseCannotOpenFile { src_path: mod_path.to_str().unwrap().to_owned(),