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
77 changes: 48 additions & 29 deletions rust/vm-package-manager/src/links/cargo.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use rayon::prelude::*;
use regex::Regex;
use std::collections::HashSet;
use std::process::Command;
use std::sync::OnceLock;
use vm_core::error::{Result, VmError};
use vm_core::vm_error;

pub fn detect_cargo_packages(packages: &[String]) -> Result<Vec<(String, String)>> {
let package_set: HashSet<&String> = packages.iter().collect();
Expand Down Expand Up @@ -43,34 +40,56 @@ pub fn detect_cargo_packages(packages: &[String]) -> Result<Vec<(String, String)
fn parse_cargo_install_list(output: &str) -> Result<Vec<(String, String)>> {
let mut installations = Vec::new();

// Regex to match cargo install list format:
// package_name v1.0.0 (/path/to/source):
static RE: OnceLock<Regex> = OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(r"^([a-zA-Z0-9_-]+)\s+[^\(]*\(([^)]+)\):$").expect("Failed to compile regex")
});

for line in output.lines() {
if let Some(captures) = re.captures(line) {
let pkg_name = match captures.get(1) {
Some(m) => m.as_str().to_string(),
None => {
vm_error!("Malformed cargo metadata line: missing package name");
continue;
}
};
let pkg_path = match captures.get(2) {
Some(m) => m.as_str(),
None => {
vm_error!("Malformed cargo metadata line: missing package path");
continue;
}
};
// Parse: package_name v1.0.0 (/path/to/source):
// Regex equivalent: ^([a-zA-Z0-9_-]+)\s+[^\(]*\(([^)]+)\):$

// Only include path-based installs (not registry installs)
if pkg_path.contains('/') && !pkg_path.starts_with("registry+") {
installations.push((pkg_name, pkg_path.to_string()));
}
// 1. Extract package name (chars until first whitespace)
let Some(first_space_idx) = line.find(char::is_whitespace) else {
continue;
};

let pkg_name_str = &line[..first_space_idx];

// Validation: [a-zA-Z0-9_-]+
if pkg_name_str.is_empty()
|| !pkg_name_str
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
continue;
}

// 2. Check line ending
if !line.ends_with("):") {
continue;
}

// 3. Find path start
// Skip package name, search for first '('
let Some(paren_relative_idx) = line[first_space_idx..].find('(') else {
continue;
};
let paren_start_idx = first_space_idx + paren_relative_idx;

// Path is between '(' and final '):'
let path_start = paren_start_idx + 1;
let path_end = line.len() - 2;

if path_start >= path_end {
continue;
}

let pkg_path = &line[path_start..path_end];

// Regex ([^)]+) implies path cannot contain ')'
if pkg_path.contains(')') {
continue;
}

// Only include path-based installs (not registry installs)
if pkg_path.contains('/') && !pkg_path.starts_with("registry+") {
installations.push((pkg_name_str.to_string(), pkg_path.to_string()));
}
}

Expand Down
Loading