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