use anyhow::{Context, Result}; use std::path::Path; /// Write .cargo/config.toml in the project's warm directory pointing /// CARGO_TARGET_DIR at the hot NVMe path. Preserves any existing non-[build] /// sections already in the file. Safe to re-run (idempotent). pub fn write_cargo_config(warm_path: &Path, hot_target_path: &Path) -> Result<()> { let cargo_dir = warm_path.join(".cargo"); std::fs::create_dir_all(&cargo_dir) .with_context(|| format!("creating .cargo dir at {}", cargo_dir.display()))?; let target = hot_target_path.to_str() .context("hot_target_path is not valid UTF-8")?; let config_path = cargo_dir.join("config.toml"); // Read existing content; strip any prior [build] section so we can replace it. let existing = if config_path.exists() { std::fs::read_to_string(&config_path)? } else { String::new() }; // Drop any leading copies of our own marker comment before stripping the // [build] section — it sits above the [build] header, outside the range // strip_section tracks, so without this it would survive every // regenerate cycle and duplicate one more time. let existing = strip_leading_marker(&existing); // Remove old [build] block (from its header to the next section or EOF). let stripped = strip_section(existing, "build"); let new_content = format!( "# claw-store managed — do not edit manually\n\ [build]\n\ target-dir = \"{}\"\n\ {}", target, stripped.trim_start() ); std::fs::write(&config_path, new_content) .with_context(|| format!("writing {}", config_path.display())) } const MANAGED_MARKER: &str = "# claw-store managed — do not edit manually"; /// Strip leading copies of `MANAGED_MARKER`, one per line, from the start of `src`. fn strip_leading_marker(src: &str) -> &str { let mut rest = src; while let Some(line_end) = rest.find('\n') { if rest[..line_end].trim() == MANAGED_MARKER { rest = &rest[line_end + 1..]; } else { break; } } rest } /// Remove a TOML section `[name]` and all its key=value lines from `src`, /// stopping at the next `[section]` header or EOF. fn strip_section(src: &str, name: &str) -> String { let header = format!("[{}]", name); let mut out = String::new(); let mut in_section = false; for line in src.lines() { let trimmed = line.trim(); if trimmed == header { in_section = true; continue; } if in_section && trimmed.starts_with('[') { in_section = false; } if !in_section { out.push_str(line); out.push('\n'); } } out } /// Returns true if .cargo/config.toml exists and points to the expected hot path. pub fn verify_cargo_config(warm_path: &Path, hot_target_path: &Path) -> Result { let config_path = warm_path.join(".cargo/config.toml"); if !config_path.exists() { return Ok(false); } let content = std::fs::read_to_string(&config_path)?; let target = hot_target_path.to_str().unwrap_or(""); Ok(content.contains(target)) } #[cfg(test)] mod tests { use super::*; use tempfile::TempDir; #[test] fn test_write_cargo_config() { let dir = TempDir::new().unwrap(); let warm = dir.path().join("my-project"); std::fs::create_dir_all(&warm).unwrap(); let hot_target: std::path::PathBuf = "/hot/targets/my-project".into(); write_cargo_config(&warm, &hot_target).unwrap(); let cargo_dir = warm.join(".cargo"); let config_file = cargo_dir.join("config.toml"); assert!(config_file.exists()); let content = std::fs::read_to_string(&config_file).unwrap(); assert!(content.contains("[build]")); assert!(content.contains("/hot/targets/my-project")); } #[test] fn test_verify_cargo_config_detects_correct() { let dir = TempDir::new().unwrap(); let warm = dir.path().join("proj"); std::fs::create_dir_all(&warm).unwrap(); let hot: std::path::PathBuf = "/hot/targets/proj".into(); write_cargo_config(&warm, &hot).unwrap(); assert!(verify_cargo_config(&warm, &hot).unwrap()); } #[test] fn test_write_cargo_config_idempotent_no_duplicate_marker() { let dir = TempDir::new().unwrap(); let warm = dir.path().join("proj"); std::fs::create_dir_all(&warm).unwrap(); let hot: std::path::PathBuf = "/hot/targets/proj".into(); write_cargo_config(&warm, &hot).unwrap(); write_cargo_config(&warm, &hot).unwrap(); write_cargo_config(&warm, &hot).unwrap(); let content = std::fs::read_to_string(warm.join(".cargo/config.toml")).unwrap(); assert_eq!(content.matches("claw-store managed").count(), 1); } #[test] fn test_write_cargo_config_heals_existing_duplicate_marker() { let dir = TempDir::new().unwrap(); let warm = dir.path().join("proj"); let cargo_dir = warm.join(".cargo"); std::fs::create_dir_all(&cargo_dir).unwrap(); std::fs::write( cargo_dir.join("config.toml"), "# claw-store managed — do not edit manually\n\ [build]\n\ target-dir = \"/hot/targets/proj\"\n\ # claw-store managed — do not edit manually\n\ [env]\n\ FOO = \"bar\"\n", ) .unwrap(); let hot: std::path::PathBuf = "/hot/targets/proj".into(); write_cargo_config(&warm, &hot).unwrap(); let content = std::fs::read_to_string(cargo_dir.join("config.toml")).unwrap(); assert_eq!(content.matches("claw-store managed").count(), 1); assert!(content.contains("FOO = \"bar\"")); } #[test] fn test_verify_cargo_config_detects_missing() { let dir = TempDir::new().unwrap(); let warm = dir.path().join("no-config-proj"); std::fs::create_dir_all(&warm).unwrap(); let hot: std::path::PathBuf = "/hot/targets/no-config-proj".into(); assert!(!verify_cargo_config(&warm, &hot).unwrap()); } }