vtcode_core/tools/ripgrep_installer/
mod.rs1mod platform;
7mod state;
8
9use crate::tools::install_support::run_version_probe;
10use crate::tools::ripgrep_binary::RIPGREP_INSTALL_COMMAND;
11use anyhow::{Result, anyhow};
12use std::process::Command;
13
14use self::platform::install_with_smart_detection;
15use self::state::{InstallLockGuard, InstallationCache};
16
17#[derive(Debug, Clone, PartialEq)]
19pub enum RipgrepStatus {
20 Available { version: String },
22 NotFound,
24 Error { reason: String },
26}
27
28impl RipgrepStatus {
29 pub fn check() -> Self {
31 debug_log("Checking ripgrep availability...");
32 let mut command = Command::new("rg");
33 command.arg("--version");
34 match run_version_probe(command) {
35 Ok(output) => {
36 if output.status.success() {
37 let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
38 if version.is_empty() {
39 debug_log("ripgrep found but returned empty version");
40 RipgrepStatus::Error {
41 reason: "rg --version returned empty output".to_string(),
42 }
43 } else {
44 debug_log(&format!("ripgrep available: {version}"));
45 RipgrepStatus::Available { version }
46 }
47 } else {
48 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
49 debug_log(&format!("ripgrep check failed: {stderr}"));
50 RipgrepStatus::Error {
51 reason: if stderr.is_empty() {
52 "rg exited with error".to_string()
53 } else {
54 stderr
55 },
56 }
57 }
58 }
59 Err(err) => {
60 if err.kind() == std::io::ErrorKind::NotFound {
61 debug_log("ripgrep not found in PATH");
62 RipgrepStatus::NotFound
63 } else {
64 debug_log(&format!("ripgrep check error: {err}"));
65 RipgrepStatus::Error { reason: err.to_string() }
66 }
67 }
68 }
69 }
70
71 pub fn install() -> Result<()> {
74 debug_log("Installation attempt started");
75
76 if matches!(Self::check(), RipgrepStatus::Available { .. }) {
77 debug_log("ripgrep already available; skipping installation");
78 return Ok(());
79 }
80
81 if std::env::var("VTCODE_RIPGREP_NO_INSTALL").is_ok() {
82 debug_log("Auto-install disabled via VTCODE_RIPGREP_NO_INSTALL");
83 return Err(anyhow!("Ripgrep auto-installation disabled via VTCODE_RIPGREP_NO_INSTALL"));
84 }
85
86 if InstallLockGuard::is_install_in_progress() {
87 debug_log("Another installation is already in progress, skipping");
88 return Err(anyhow!("Ripgrep installation already in progress"));
89 }
90
91 let _lock = InstallLockGuard::acquire()?;
92 debug_log("Installation lock acquired");
93
94 if !InstallationCache::is_stale()
95 && let Ok(cache) = InstallationCache::load()
96 && cache.status == "failed"
97 {
98 let reason = cache.failure_reason.as_deref().unwrap_or("unknown reason");
99 debug_log(&format!("Cache shows previous failure: {reason}"));
100 return Err(anyhow!("Previous installation attempt failed ({reason}). Not retrying for 24 hours."));
101 }
102
103 let result = install_with_smart_detection();
104
105 match result {
106 Ok(()) => match Self::check() {
107 RipgrepStatus::Available { .. } => {
108 debug_log("Installation verified successfully");
109 InstallationCache::mark_success("auto-detected");
110 Ok(())
111 }
112 status => {
113 let msg = format!("Installation verification failed: {status:?}");
114 debug_log(&msg);
115 InstallationCache::mark_failed("auto-detected", &msg);
116 Err(anyhow!(msg))
117 }
118 },
119 Err(err) => {
120 let msg = err.to_string();
121 debug_log(&format!("Installation failed: {msg}"));
122 InstallationCache::mark_failed("all-methods", &msg);
123 Err(anyhow!(msg))
124 }
125 }
126 }
127
128 pub fn ensure_available() -> Result<Self> {
130 match Self::check() {
131 status @ RipgrepStatus::Available { .. } => Ok(status),
132 RipgrepStatus::NotFound => {
133 tracing::warn!("ripgrep not found, run `{RIPGREP_INSTALL_COMMAND}` to install");
134 Self::install()?;
135 Ok(Self::check())
136 }
137 status => Ok(status),
138 }
139 }
140}
141
142pub(super) fn debug_log(message: &str) {
143 if std::env::var("VTCODE_DEBUG_RIPGREP").is_ok() {
144 tracing::debug!(message, "ripgrep");
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_ripgrep_status_check() {
154 let status = RipgrepStatus::check();
155 tracing::debug!(?status, "Ripgrep status");
156 }
157}