Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions connectrpc-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
//! call [`Config::use_buf`]. To avoid both, precompile a `FileDescriptorSet`
//! once and ship it alongside your source via [`Config::descriptor_set`].

use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;

Expand Down Expand Up @@ -251,7 +250,7 @@ impl Config {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::File::create(&path)?.write_all(file.content.as_bytes())?;
write_if_changed(&path, file.content.as_bytes())?;
let pkg = name_to_package.get(&file.name).cloned().unwrap_or_default();
entries.push((file.name.clone(), pkg));
}
Expand All @@ -260,7 +259,7 @@ impl Config {
if let Some(ref include_name) = self.include_file {
let include_src = generate_include_file(&entries, relative_includes);
let include_path = out_dir.join(include_name);
std::fs::File::create(&include_path)?.write_all(include_src.as_bytes())?;
write_if_changed(&include_path, include_src.as_bytes())?;
}

// 6. Cargo re-run triggers.
Expand All @@ -281,6 +280,19 @@ impl Default for Config {
}
}

/// Write `content` to `path` only if the file doesn't already exist with
/// identical content. Cargo's rebuild decision for `include!`-ed files is
/// mtime-based, so an unconditional write here would cascade into
/// recompiling every downstream crate whenever any `.proto` is touched.
fn write_if_changed(path: &Path, content: &[u8]) -> std::io::Result<()> {
if let Ok(existing) = std::fs::read(path)
&& existing == content
{
return Ok(());
}
std::fs::write(path, content)
}

/// Run `protoc` and return the serialized `FileDescriptorSet`.
fn run_protoc(files: &[PathBuf], includes: &[PathBuf]) -> Result<Vec<u8>> {
let protoc = std::env::var("PROTOC").unwrap_or_else(|_| "protoc".to_string());
Expand Down Expand Up @@ -678,4 +690,37 @@ mod tests {
vec!["my/pkg/svc.proto".to_string(), "top.proto".to_string()]
);
}

#[test]
fn write_if_changed_creates_new_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("new.rs");
write_if_changed(&path, b"hello").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello");
}

#[test]
fn write_if_changed_skips_identical_content() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("same.rs");
std::fs::write(&path, b"content").unwrap();
let mtime_before = std::fs::metadata(&path).unwrap().modified().unwrap();

// Sleep briefly so a write would produce a distinguishable mtime.
std::thread::sleep(std::time::Duration::from_millis(50));

write_if_changed(&path, b"content").unwrap();
let mtime_after = std::fs::metadata(&path).unwrap().modified().unwrap();
assert_eq!(mtime_before, mtime_after);
}

#[test]
fn write_if_changed_overwrites_different_content() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("changed.rs");
std::fs::write(&path, b"old").unwrap();

write_if_changed(&path, b"new").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"new");
}
}
Loading