use crate::e_target::TargetKind;
use anyhow::{anyhow, Result};
use glob::glob;
use log::trace;
use std::error::Error;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{fs, io};
use toml::Value;
pub fn locate_manifest(workspace: bool) -> Result<String, Box<dyn Error>> {
let mut args = vec!["locate-project", "--message-format", "plain"];
if workspace {
args.push("--workspace");
}
let output = Command::new("cargo").args(&args).output()?;
let manifest_from_cargo = if output.status.success() {
let m = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !m.is_empty() {
Some(m)
} else {
None
}
} else {
None
};
let fallback_path = Path::new("src-tauri").join("Cargo.toml");
if fallback_path.exists() {
return Ok(fallback_path.to_string_lossy().into_owned());
}
if let Some(m) = manifest_from_cargo {
return Ok(m);
}
Err("No Cargo.toml found".into())
}
pub fn collect_workspace_members(
workspace_manifest: &str,
) -> Result<Vec<(String, PathBuf)>, Box<dyn Error>> {
let manifest_path = Path::new(workspace_manifest);
let workspace_root = manifest_path
.parent()
.ok_or("Cannot determine workspace root")?;
let manifest_contents = fs::read_to_string(workspace_manifest)?;
let value: Value = manifest_contents.parse::<Value>()?;
let mut members = Vec::new();
if let Some(ws) = value.get("workspace") {
if let Some(member_array) = ws.get("members").and_then(|v| v.as_array()) {
for member in member_array {
if let Some(member_str) = member.as_str() {
if member_str.ends_with("/*") {
let dir_pattern = member_str.trim_end_matches("/*");
let glob_pattern = format!("{}/**/Cargo.toml", dir_pattern);
for entry in glob(&glob_pattern)? {
match entry {
Ok(path) => {
if path.exists()
&& path.file_name().and_then(|name| name.to_str())
== Some("Cargo.toml")
{
let member_name = path
.parent()
.and_then(|p| p.file_name())
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| "unknown".to_string());
members.push((member_name, path));
}
}
Err(_) => continue, }
}
} else {
let member_clean = if member_str.contains('*') {
member_str.trim_end_matches("/*")
} else {
member_str
};
let member_path = workspace_root.join(member_clean);
let member_manifest = member_path.join("Cargo.toml");
if member_manifest.exists() {
members.push((member_clean.to_string(), member_manifest));
}
}
}
}
}
}
Ok(members)
}
#[allow(dead_code)]
pub(crate) fn maybe_patch_manifest_for_run(manifest_path: &Path) -> Result<Option<String>> {
let output = Command::new("cargo")
.args(["metadata", "--no-deps", "--manifest-path"])
.arg(manifest_path)
.output()?;
let stderr_str = String::from_utf8_lossy(&output.stderr);
let workspace_error_marker = "current package believes it's in a workspace when it's not:";
if stderr_str.contains(workspace_error_marker) {
let original = fs::read_to_string(manifest_path)?;
if !original.contains("[workspace]") {
let patched = format!("{}\n[workspace]\n", original);
fs::write(manifest_path, &patched)?;
return Ok(Some(original));
}
}
Ok(None)
}
pub fn find_manifest_dir() -> io::Result<PathBuf> {
let mut dir = std::env::current_dir()?;
loop {
if dir.join("Cargo.toml").exists() {
return Ok(dir);
}
if !dir.pop() {
break;
}
}
let fallback = Path::new("src-tauri").join("Cargo.toml");
if fallback.exists() {
return Ok(fallback
.parent()
.expect("src-tauri/Cargo.toml has no parent")
.to_path_buf());
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"Could not locate Cargo.toml in the current or parent directories.",
))
}
pub fn find_manifest_dir_from(start: &Path) -> io::Result<PathBuf> {
let mut dir = start.to_path_buf();
loop {
trace!("Checking {}", dir.join("Cargo.toml").display());
if dir.join("Cargo.toml").exists() {
return Ok(dir);
}
if !dir.pop() {
break;
}
}
let fallback = Path::new("src-tauri").join("Cargo.toml");
if fallback.exists() {
return Ok(fallback
.parent()
.expect("src-tauri/Cargo.toml has no parent")
.to_path_buf());
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"Could not locate Cargo.toml in the current or parent directories.",
))
}
pub fn get_required_features_from_manifest(
manifest_path: &Path,
kind: &TargetKind,
target_name: &str,
) -> Option<String> {
trace!(
"Searching for required features in manifest: {}",
manifest_path.display()
);
let content = fs::read_to_string(manifest_path).ok()?;
let value: Value = content.parse().ok()?;
let section = kind.section_name();
if section.is_empty() {
return None;
}
if let Some(targets) = value.get(section).and_then(|v| v.as_array()) {
for entry in targets {
if let Some(name) = entry.get("name").and_then(|v| v.as_str()) {
if name == target_name {
if let Some(req_feats) =
entry.get("required-features").and_then(|v| v.as_array())
{
let feats = req_feats
.iter()
.filter_map(|f| f.as_str())
.collect::<Vec<_>>()
.join(",");
if !feats.is_empty() {
return Some(feats);
}
}
}
}
}
}
if value.get("workspace").is_some() {
if let Some(manifest_str) = manifest_path.to_str() {
if let Ok(members) = collect_workspace_members(manifest_str) {
for (member_name, member_manifest_path) in members {
trace!(
"Checking workspace {}: {}",
member_name,
member_manifest_path.display()
);
let is_same_manifest = member_manifest_path.canonicalize().ok()
== manifest_path.canonicalize().ok();
if member_name == "." || is_same_manifest {
trace!(
"Skipping current workspace member: {}",
member_manifest_path.display()
);
continue;
}
if let Some(feats) = get_required_features_from_manifest(
&member_manifest_path,
kind,
target_name,
) {
return Some(feats);
}
}
}
}
}
None
}
pub fn find_candidate_name(
manifest_toml: &Value,
table_key: &str,
candidate: &Path,
manifest_path: &Path,
) -> Option<String> {
manifest_toml
.get(table_key)
.and_then(|v| v.as_array())
.and_then(|entries| {
let manifest_parent = manifest_path.parent().unwrap_or_else(|| Path::new(""));
let candidate_abs = std::fs::canonicalize(candidate).ok();
let candidate_stem = candidate.file_stem().and_then(|s| s.to_str());
let explicit = entries.iter().find_map(|entry| {
entry.get("path").and_then(|p| p.as_str()).and_then(|rel| {
entry.get("name").and_then(|n| n.as_str()).and_then(|name| {
candidate_abs.as_ref().and_then(|abs| {
std::fs::canonicalize(manifest_parent.join(rel))
.ok()
.and_then(|expected| {
if expected == *abs {
Some(name.to_string())
} else {
None
}
})
})
})
})
});
let stem_match = explicit.clone().or_else(|| {
candidate_stem.and_then(|stem| {
entries.iter().find_map(|entry| {
entry.get("name").and_then(|n| n.as_str()).and_then(|name| {
if name == stem {
Some(name.to_string())
} else {
None
}
})
})
})
});
stem_match
})
}
use crate::e_target::{CargoTarget, TargetOrigin};
#[allow(clippy::type_complexity)]
pub fn get_runnable_targets(
manifest_path: &Path,
) -> anyhow::Result<(
Vec<CargoTarget>,
Vec<CargoTarget>,
Vec<CargoTarget>,
Vec<CargoTarget>,
)> {
let content = fs::read_to_string(manifest_path)?;
let value: Value = content.parse()?;
let project_root = manifest_path
.parent()
.ok_or(anyhow!("Unable to determine project root"))?;
let is_extended = project_root
.parent()
.and_then(|p| p.file_name())
.map(|s| s.to_string_lossy().eq_ignore_ascii_case("examples"))
.unwrap_or(false);
let mut bins = Vec::new();
if let Some(bin_array) = value.get("bin").and_then(|v| v.as_array()) {
for entry in bin_array {
if let Some(name) = entry.get("name").and_then(|v| v.as_str()) {
let target_opt = if let Some(path_str) = entry.get("path").and_then(|v| v.as_str())
{
let candidate = project_root.join(path_str);
if candidate.is_file() {
CargoTarget::from_source_file(
OsStr::new(name),
&candidate,
manifest_path,
false,
false,
)
} else if candidate.is_dir() {
CargoTarget::from_folder(&candidate, manifest_path, false, true)
} else {
None
}
} else {
let candidate = project_root.join("src").join(format!("{}.rs", name));
CargoTarget::from_source_file(
OsStr::new(name),
&candidate,
manifest_path,
false,
false,
)
};
if let Some(target) = target_opt {
bins.push(target);
}
}
}
}
if let Some(pkg) = value
.get("package")
.and_then(|v| v.get("name"))
.and_then(|v| v.as_str())
{
if !bins.iter().any(|t| t.name == pkg) {
let candidate = if project_root.join("src").join("main.rs").exists() {
project_root.join("src").join("main.rs")
} else if project_root.join("main.rs").exists() {
project_root.join("main.rs")
} else {
PathBuf::new()
};
if !candidate.as_os_str().is_empty() {
let candidate = fs::canonicalize(&candidate).unwrap_or(candidate.to_path_buf());
if let Some(mut target) = CargoTarget::from_source_file(
OsStr::new(pkg),
&candidate,
manifest_path,
false,
false,
) {
target.origin = Some(TargetOrigin::DefaultBinary(candidate));
bins.push(target);
}
}
}
}
let bin_dir = project_root.join("src").join("bin");
if bin_dir.exists() && bin_dir.is_dir() {
for entry in fs::read_dir(&bin_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("rs") {
if let Some(stem) = path.file_stem() {
let name = stem.to_string_lossy().to_string();
if !bins.iter().any(|t| t.name == name) {
if let Some(target) =
CargoTarget::from_source_file(stem, &path, manifest_path, false, false)
{
bins.push(target);
}
}
}
}
}
}
let mut examples = Vec::new();
if let Some(example_array) = value.get("example").and_then(|v| v.as_array()) {
for entry in example_array {
if let Some(name) = entry.get("name").and_then(|v| v.as_str()) {
let target_opt = if let Some(path_str) = entry.get("path").and_then(|v| v.as_str())
{
let candidate = project_root.join(path_str);
CargoTarget::from_source_file(
OsStr::new(name),
&candidate,
manifest_path,
true,
false,
)
} else {
let candidate = project_root.join(format!("examples/{}.rs", name));
CargoTarget::from_source_file(
OsStr::new(name),
&candidate,
manifest_path,
true,
false,
)
};
if let Some(target) = target_opt {
examples.push(target);
}
}
}
}
let scanned_examples = crate::e_discovery::scan_examples_directory(manifest_path, "examples")?;
for ex in scanned_examples {
if !examples.iter().any(|t| t.name == ex.name) {
let mut t = ex;
if is_extended {
t.kind = TargetKind::ExtendedExample;
t.extended = true;
}
examples.push(t);
}
}
let scanned_examples =
crate::e_discovery::scan_examples_directory(manifest_path, "experiments")?;
for ex in scanned_examples {
if !examples.iter().any(|t| t.name == ex.name) {
let mut t = ex;
if is_extended {
t.kind = TargetKind::ExtendedExample;
t.extended = true;
}
examples.push(t);
}
}
let mut benches = Vec::new();
if let Some(bench_array) = value.get("bench").and_then(|v| v.as_array()) {
for entry in bench_array {
if let Some(name) = entry.get("name").and_then(|v| v.as_str()) {
benches.push(CargoTarget {
name: name.to_string(),
display_name: name.to_string(),
manifest_path: manifest_path.to_path_buf(),
kind: TargetKind::Bench,
extended: false,
toml_specified: false,
origin: None,
});
}
}
}
let mut tests = Vec::new();
let scanned_tests = crate::e_discovery::scan_tests_directory(manifest_path)?;
for test_name in scanned_tests {
let candidate = project_root.join("tests").join(format!("{}.rs", test_name));
tests.push(CargoTarget {
name: test_name.clone(),
display_name: test_name,
manifest_path: manifest_path.to_path_buf(),
kind: TargetKind::Test,
extended: false,
toml_specified: false,
origin: Some(TargetOrigin::SingleFile(candidate)),
});
}
Ok((bins, examples, benches, tests))
}