diff --git a/Cargo.lock b/Cargo.lock index 3db5c45d..ee31955a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -876,7 +876,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fbuild-bench-fastled-examples" -version = "2.3.1" +version = "2.3.3" dependencies = [ "fbuild-library-select", "fbuild-packages", @@ -887,7 +887,7 @@ dependencies = [ [[package]] name = "fbuild-build" -version = "2.3.1" +version = "2.3.3" dependencies = [ "async-trait", "blake3", @@ -919,7 +919,7 @@ dependencies = [ [[package]] name = "fbuild-cli" -version = "2.3.1" +version = "2.3.3" dependencies = [ "blake3", "clap", @@ -949,7 +949,7 @@ dependencies = [ [[package]] name = "fbuild-config" -version = "2.3.1" +version = "2.3.3" dependencies = [ "fbuild-core", "include_dir", @@ -963,7 +963,7 @@ dependencies = [ [[package]] name = "fbuild-core" -version = "2.3.1" +version = "2.3.3" dependencies = [ "libc", "prost", @@ -982,7 +982,7 @@ dependencies = [ [[package]] name = "fbuild-daemon" -version = "2.3.1" +version = "2.3.3" dependencies = [ "async-trait", "axum", @@ -1022,7 +1022,7 @@ dependencies = [ [[package]] name = "fbuild-deploy" -version = "2.3.1" +version = "2.3.3" dependencies = [ "async-trait", "espflash", @@ -1045,7 +1045,7 @@ dependencies = [ [[package]] name = "fbuild-header-scan" -version = "2.3.1" +version = "2.3.3" dependencies = [ "criterion", "rayon", @@ -1055,7 +1055,7 @@ dependencies = [ [[package]] name = "fbuild-library-select" -version = "2.3.1" +version = "2.3.3" dependencies = [ "bincode", "blake3", @@ -1074,7 +1074,7 @@ dependencies = [ [[package]] name = "fbuild-packages" -version = "2.3.1" +version = "2.3.3" dependencies = [ "axum", "bzip2", @@ -1101,14 +1101,14 @@ dependencies = [ [[package]] name = "fbuild-paths" -version = "2.3.1" +version = "2.3.3" dependencies = [ "fbuild-core", ] [[package]] name = "fbuild-python" -version = "2.3.1" +version = "2.3.3" dependencies = [ "base64", "fbuild-core", @@ -1130,7 +1130,7 @@ dependencies = [ [[package]] name = "fbuild-serial" -version = "2.3.1" +version = "2.3.3" dependencies = [ "async-trait", "base64", @@ -1152,7 +1152,7 @@ dependencies = [ [[package]] name = "fbuild-test-support" -version = "2.3.1" +version = "2.3.3" dependencies = [ "fbuild-config", "fbuild-header-scan", diff --git a/Cargo.toml b/Cargo.toml index a14935ae..22133d51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ exclude = ["dylints/ban_raw_subprocess"] libraries = [{ path = "dylints/*" }] [workspace.package] -version = "2.3.2" +version = "2.3.3" edition = "2021" rust-version = "1.94.1" license = "MIT OR Apache-2.0" diff --git a/crates/fbuild-build/src/compiler.rs b/crates/fbuild-build/src/compiler.rs index 403ec971..972af755 100644 --- a/crates/fbuild-build/src/compiler.rs +++ b/crates/fbuild-build/src/compiler.rs @@ -652,7 +652,20 @@ pub fn compile_source( tracing::info!("compile: {}", args.join(" ")); } - let result = run_command(&args_ref, compile_cwd.as_deref(), None, None)?; + let mut result = run_command(&args_ref, compile_cwd.as_deref(), None, None)?; + + if !result.success() { + if let Some(zccache) = compiler_cache { + if crate::zccache::output_has_stale_daemon_error(&result.stdout, &result.stderr) { + tracing::warn!( + "zccache protocol mismatch detected; stopping daemon and retrying compile" + ); + crate::zccache::stop(zccache)?; + crate::zccache::ensure_running(zccache)?; + result = run_command(&args_ref, compile_cwd.as_deref(), None, None)?; + } + } + } if result.success() { std::fs::write(command_hash_path(output), rebuild_signature)?; diff --git a/crates/fbuild-build/src/managed_zccache.rs b/crates/fbuild-build/src/managed_zccache.rs index 01a81a49..2df5338c 100644 --- a/crates/fbuild-build/src/managed_zccache.rs +++ b/crates/fbuild-build/src/managed_zccache.rs @@ -24,12 +24,12 @@ use fbuild_core::{FbuildError, Result}; /// The zccache release fbuild pins. Bump in lockstep with the floor that /// the rest of the toolchain expects. -pub const MANAGED_ZCCACHE_VERSION: &str = "1.12.9"; +pub const MANAGED_ZCCACHE_VERSION: &str = "1.12.10"; /// GitHub release tag for [`MANAGED_ZCCACHE_VERSION`]. The zccache release /// workflow tags without a `v` prefix, while the per-asset filenames carry /// `v` — keep both spellings in sync when bumping. -const RELEASE_TAG: &str = "1.12.9"; +const RELEASE_TAG: &str = "1.12.10"; /// Source repository for managed downloads. const REPO: &str = "zackees/zccache"; diff --git a/crates/fbuild-build/src/zccache.rs b/crates/fbuild-build/src/zccache.rs index d5321961..22cc947d 100644 --- a/crates/fbuild-build/src/zccache.rs +++ b/crates/fbuild-build/src/zccache.rs @@ -213,6 +213,47 @@ pub fn ensure_running(zccache: &Path) -> Result<()> { if output.status.success() { tracing::info!("zccache daemon running"); Ok(()) + } else if output_has_stale_daemon_error( + &String::from_utf8_lossy(&output.stdout), + &String::from_utf8_lossy(&output.stderr), + ) { + tracing::warn!("zccache daemon appears stale; stopping and retrying start"); + let _ = stop(zccache); + std::thread::sleep(std::time::Duration::from_millis(250)); + + let mut retry_cmd = std::process::Command::new(zccache); + retry_cmd + .arg("start") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + const CREATE_NO_WINDOW: u32 = 0x08000000; + retry_cmd.creation_flags(CREATE_NO_WINDOW); + } + + let retry = retry_cmd.output().map_err(|e| { + FbuildError::BuildFailed(format!( + "failed to spawn zccache daemon via `{}` start: {}", + zccache.display(), + e + )) + })?; + if retry.status.success() { + tracing::info!("zccache daemon running after stale-daemon recovery"); + Ok(()) + } else { + let message = format_zccache_start_failure( + zccache, + retry.status.to_string(), + &retry.stdout, + &retry.stderr, + ); + tracing::warn!("{message}"); + Err(FbuildError::BuildFailed(message)) + } } else { let message = format_zccache_start_failure( zccache, @@ -225,6 +266,55 @@ pub fn ensure_running(zccache: &Path) -> Result<()> { } } +/// Stop the zccache daemon for this user, if one is running. +pub fn stop(zccache: &Path) -> Result<()> { + let mut cmd = std::process::Command::new(zccache); + cmd.arg("stop") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + const CREATE_NO_WINDOW: u32 = 0x08000000; + cmd.creation_flags(CREATE_NO_WINDOW); + } + + let output = cmd.output().map_err(|e| { + FbuildError::BuildFailed(format!( + "failed to spawn zccache daemon via `{}` stop: {}", + zccache.display(), + e + )) + })?; + + if output.status.success() { + Ok(()) + } else { + Err(FbuildError::BuildFailed(format_zccache_start_failure( + zccache, + output.status, + &output.stdout, + &output.stderr, + ))) + } +} + +/// zccache can leave a previous-version daemon running across fbuild upgrades. +/// The new client then fails wrapped compiles with a protocol mismatch until +/// the old daemon is stopped. +pub fn output_has_protocol_mismatch(stdout: &str, stderr: &str) -> bool { + stdout.contains("protocol version mismatch") || stderr.contains("protocol version mismatch") +} + +pub fn output_has_stale_daemon_error(stdout: &str, stderr: &str) -> bool { + output_has_protocol_mismatch(stdout, stderr) + || stdout.contains("lost connection to daemon") + || stderr.contains("lost connection to daemon") + || stdout.contains("not accepting connections") + || stderr.contains("not accepting connections") +} + fn format_zccache_start_failure( zccache: &Path, status: impl std::fmt::Display, @@ -597,6 +687,23 @@ mod tests { assert!(message.contains("fbuild daemon logs")); } + #[test] + fn detects_zccache_protocol_mismatch_from_stderr_or_stdout() { + let mismatch = + "zccache[err][R]: broken connection to daemon: protocol error: protocol version mismatch: expected v16, received v15"; + + assert!(output_has_protocol_mismatch("", mismatch)); + assert!(output_has_protocol_mismatch(mismatch, "")); + assert!(output_has_stale_daemon_error( + "", + "zccache[err][R]: lost connection to daemon (no response)" + )); + assert!(!output_has_protocol_mismatch( + "ordinary stdout", + "ordinary stderr" + )); + } + #[test] fn path_arg_for_compile_cwd_returns_workspace_relative_path() { let tmp = tempfile::TempDir::new().unwrap(); diff --git a/pyproject.toml b/pyproject.toml index 9b5508bf..dde0d9e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fbuild" -version = "2.3.2" +version = "2.3.3" description = "PlatformIO-compatible embedded build tool (Rust implementation)" readme = "README.md" requires-python = ">=3.10"