use crate::e_processmanager::ProcessManager;
use crate::{e_target::TargetOrigin, prelude::*};
use crate::e_cargocommand_ext::CargoProcessHandle;
use crate::e_target::CargoTarget;
#[cfg(feature = "uses_plugins")]
use crate::plugins::plugin_api::Target as PluginTarget;
use anyhow::Result;
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use which::which;
pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
static CTRL_C_COUNT: AtomicUsize = AtomicUsize::new(0);
pub fn reset_ctrl_c_count() {
CTRL_C_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
}
pub fn take_process_results(pid: u32) -> Option<CargoProcessHandle> {
let mut global = GLOBAL_CHILDREN.lock().ok()?;
let handle = global.remove(&pid)?;
Arc::try_unwrap(handle)
.ok()? .into_inner()
.ok() }
pub fn get_process_results_in_place(
pid: u32,
) -> Option<crate::e_cargocommand_ext::CargoProcessResult> {
let global = GLOBAL_CHILDREN.lock().ok()?; let handle = global.get(&pid)?.clone(); let handle = handle.lock().ok()?; Some(handle.result.clone()) }
pub fn register_ctrlc_handler(process_manager: Arc<ProcessManager>) -> Result<(), Box<dyn Error>> {
println!("Registering Ctrl+C handler...");
ctrlc::set_handler(move || {
let count = CTRL_C_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
eprintln!("Ctrl+C pressed");
match process_manager.kill_one() {
Ok(true) => {
eprintln!("Process was successfully terminated.");
reset_ctrl_c_count();
return; }
Ok(false) => {
eprintln!("No process was killed this time.");
}
Err(e) => {
eprintln!("Error killing process: {:?}", e);
}
}
if count == 3 {
eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
std::process::exit(0);
} else if count == 2 {
eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
} else {
eprintln!("Ctrl+C pressed {} times, no child process running.", count);
}
})?;
Ok(())
}
pub async fn open_ai_summarize_for_target(target: &CargoTarget) {
let origin_path = match &target.origin {
Some(TargetOrigin::SingleFile(path)) | Some(TargetOrigin::DefaultBinary(path)) => path,
_ => return,
};
let exe_path = match which("cargoe_ai_summarize") {
Ok(path) => path,
Err(err) => {
eprintln!("Error: 'cargoe_ai_summarize' not found in PATH: {}", err);
return;
}
};
let mut cmd = Command::new(exe_path);
cmd.arg("--streaming");
cmd.arg("--stdin");
cmd.arg(origin_path);
cmd.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let child = cmd.spawn();
let status = child
.expect("Failed to spawn command")
.wait()
.expect("Failed to wait for command");
if !status.success() {
eprintln!("Command exited with status: {}", status);
}
}
fn library_hint(lib: &str) -> &str {
match lib {
"javascriptcoregtk-4.1" => "libjavascriptcoregtk-4.1-dev",
"libsoup-3.0" => "libsoup-3.0-dev",
"webkit2gtk-4.1" => "libwebkit2gtk-4.1-dev",
"openssl" => "libssl-dev",
_ => lib, }
}
#[cfg(feature = "equivalent")]
pub fn run_equivalent_example(
cli: &crate::Cli,
) -> Result<std::process::ExitStatus, Box<dyn Error>> {
let mut cmd = Command::new("cargo");
cmd.args([
"run",
"--example",
cli.explicit_example.as_deref().unwrap_or(""),
]);
if !cli.extra.is_empty() {
cmd.arg("--").args(cli.extra.clone());
}
use std::process::Stdio;
cmd.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let status = cmd.status()?;
std::process::exit(status.code().unwrap_or(1));
}
pub fn run_example(
manager: Arc<ProcessManager>,
cli: &crate::Cli,
target: &crate::e_target::CargoTarget,
) -> anyhow::Result<Option<std::process::ExitStatus>> {
crate::e_runall::set_rustflags_if_quiet(cli.quiet);
let current_bin = env!("CARGO_PKG_NAME");
if target.kind == crate::e_target::TargetKind::Binary && target.name == current_bin {
println!(
"Skipping automatic run: {} is the same as the running binary",
target.name
);
return Ok(None);
}
#[cfg(feature = "uses_plugins")]
if target.kind == crate::e_target::TargetKind::Plugin {
if let Some(crate::e_target::TargetOrigin::Plugin { plugin_path, .. }) = &target.origin {
let cwd = std::env::current_dir()?;
let ext = plugin_path
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
let plugin: Box<dyn crate::plugins::plugin_api::Plugin> = match ext {
"lua" => {
#[cfg(feature = "uses_lua")]
{
Box::new(crate::plugins::lua_plugin::LuaPlugin::load(
plugin_path,
cli,
manager.clone(),
)?)
}
#[cfg(not(feature = "uses_lua"))]
{
return Err(anyhow::anyhow!("Lua plugin support is not enabled"));
}
}
"rhai" => {
#[cfg(feature = "uses_rhai")]
{
Box::new(crate::plugins::rhai_plugin::RhaiPlugin::load(
plugin_path,
cli,
manager.clone(),
)?)
}
#[cfg(not(feature = "uses_rhai"))]
{
return Err(anyhow::anyhow!("Rhai plugin support is not enabled"));
}
}
"wasm" => {
#[cfg(feature = "uses_wasm")]
{
if let Some(wp) =
crate::plugins::wasm_plugin::WasmPlugin::load(plugin_path)?
{
Box::new(wp)
} else {
Box::new(
crate::plugins::wasm_export_plugin::WasmExportPlugin::load(
plugin_path,
)?
.expect("Failed to load export plugin"),
)
}
}
#[cfg(not(feature = "uses_wasm"))]
{
return Err(anyhow::anyhow!("WASM plugin support is not enabled"));
}
}
"dll" => {
#[cfg(feature = "uses_wasm")]
{
Box::new(
crate::plugins::wasm_export_plugin::WasmExportPlugin::load(
plugin_path,
)?
.expect("Failed to load export plugin"),
)
}
#[cfg(not(feature = "uses_wasm"))]
{
return Err(anyhow::anyhow!("WASM export plugin support is not enabled"));
}
}
other => {
return Err(anyhow::anyhow!("Unknown plugin extension: {}", other));
}
};
let plugin_target = PluginTarget::from(target.clone());
let output = plugin.run(&cwd, &plugin_target)?;
if !output.is_empty() {
if let Ok(code) = output[0].parse::<i32>() {
eprintln!("Plugin exited with code: {}", code);
}
for line in &output[1..] {
println!("{}", line);
}
}
return Ok(None);
}
}
let manifest_path = PathBuf::from(target.manifest_path.clone());
let mut builder = crate::e_command_builder::CargoCommandBuilder::new(
&target.name,
&manifest_path,
&cli.subcommand,
cli.filter,
cli.cached,
cli.default_binary_is_runner,
cli.quiet || cli.json_all_targets,
cli.detached,
cli.cwd_wsr,
)
.with_target(target)
.with_required_features(&target.manifest_path, target)
.with_cli(cli);
let mut cmd = builder.clone().build_command();
let mut exec_dir: Option<std::path::PathBuf> = None;
if let Some(ref dir) = builder.execution_dir {
cmd.current_dir(dir);
exec_dir = Some(dir.as_path().to_path_buf());
} else if cli.cwd_wsr {
if let Some(dir) = target.manifest_path.parent() {
cmd.current_dir(dir);
exec_dir = Some(dir.to_path_buf());
}
}
let full_command = format!(
"{} {}",
cmd.get_program().to_string_lossy(),
cmd.get_args()
.map(|arg| arg.to_string_lossy())
.collect::<Vec<_>>()
.join(" ")
);
println!("Running: {} [from {:?}]", full_command, exec_dir);
let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)?;
let a_blder = Arc::new(builder.clone());
let pid = a_blder.run(|pid, handle| {
manager.register(pid, handle);
})?;
let result = manager.wait(pid, None)?;
if result
.exit_status
.map_or(false, |status| status.code() == Some(101))
{
println!(
"ProcessManager senses pid {} cargo error, running again to capture and analyze",
pid,
);
match builder.clone().capture_output() {
Ok(output) => {
let system_lib_regex = Regex::new(
r"\s*The system library `([^`]+)` required by crate `([^`]+)` was not found\.",
)
.unwrap();
if let Some(captures) = system_lib_regex.captures(&output) {
let library = &captures[1];
let crate_name = &captures[2];
println!(
"cargo-e detected missing system library '{}' required by crate '{}'.",
library, crate_name
);
println!(
"You might need to install '{}' via your system package manager.",
library
);
println!("For example:");
println!(
" • Debian/Ubuntu: sudo apt install {}",
library_hint(library)
);
println!(" • Fedora: sudo dnf install {}", library_hint(library));
println!(" • Arch: sudo pacman -S {}", library_hint(library));
println!(
" • macOS (Homebrew): brew install {}",
library_hint(library)
);
std::process::exit(0);
} else if output.contains("error: failed to load manifest for workspace member") {
println!("cargo-e error: failed to load manifest for workspace member, please check your workspace configuration.");
println!("cargo-e autorecovery: removing manfifest path from argument and changing to parent of Cargo.toml.");
let cwd = target
.manifest_path
.parent()
.unwrap_or_else(|| Path::new("."));
builder.execution_dir = Some(cwd.to_path_buf());
if let Some(pos) = builder.args.iter().position(|arg| arg == "--manifest-path")
{
builder.args.remove(pos); if pos < builder.args.len() {
builder.args.remove(pos); }
}
let mut cmd = builder.clone().build_command();
cmd.current_dir(cwd);
let mut child = cmd.spawn()?;
let status = child.wait()?;
return Ok(Some(status)); }
if output.contains("no such command: `tauri`") {
println!("cargo tauri is not installed, please install it with cargo install tauri-cli");
match crate::e_prompts::yesno(
"Do you want to install tauri-cli?",
Some(true), ) {
Ok(Some(true)) => {
println!("Installing tauri-cli...");
match spawn_cargo_process(&["install", "tauri-cli"]) {
Ok(mut child) => {
child.wait().ok(); } Err(e) => {
eprintln!("Error installing tauri-cli: {}", e);
}
}
}
Ok(Some(false)) => {}
Ok(None) => {
println!("Installation cancelled (timeout or invalid input).");
}
Err(e) => {
eprintln!("Error during prompt: {}", e);
}
}
} else if output.contains("error: no such command: `leptos`") {
println!("cargo-leptos is not installed, please install it with cargo install cargo-leptos");
match crate::e_prompts::yesno(
"Do you want to install cargo-leptos?",
Some(true), ) {
Ok(Some(true)) => {
println!("Installing cargo-leptos...");
match spawn_cargo_process(&["install", "cargo-leptos"]) {
Ok(mut child) => {
child.wait().ok(); } Err(e) => {
eprintln!("Error installing cargo-leptos: {}", e);
}
}
}
Ok(Some(false)) => {}
Ok(None) => {
println!("Installation cancelled (timeout or invalid input).");
}
Err(e) => {
eprintln!("Error during prompt: {}", e);
}
}
} else if output.contains("Unable to find libclang")
|| output.contains("couldn't find any valid shared libraries matching: ['clang.dll', 'libclang.dll']")
{
crate::e_autosense::auto_sense_llvm();
} else if output.contains("no such command: `dx`") {
println!("cargo dx is not installed, please install it with cargo install dioxus-cli");
} else if output.contains("no such command: `scriptisto`") {
println!("cargo scriptisto is not installed, please install it with cargo install scriptisto");
} else if output.contains("no such command: `rust-script`") {
println!("cargo rust-script is not installed, please install it with cargo install rust-script");
} else if output.contains(
"No platform feature enabled. Please enable one of the following features:",
) {
println!("cargo e sees a dioxus issue; maybe a prompt in the future or auto-resolution.");
} else {
}
}
Err(e) => {
eprintln!("Error running cargo: {}", e);
}
}
}
result.print_exact();
result.print_compact();
result.print_short();
manager.print_prefixed_summary();
let errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.level.eq("error"))
.collect();
let error_width = errors.len().to_string().len().max(1);
let line: Vec<String> = errors
.iter()
.enumerate()
.map(|(i, diag)| {
let index = format!("{:0width$}", i + 1, width = error_width);
let lineref = if diag.lineref.is_empty() {
""
} else {
&diag.lineref
};
let (filename, goto) = if let Some((file, line, col)) = diag
.lineref
.split_once(':')
.and_then(|(f, rest)| rest.split_once(':').and_then(|(l, c)| Some((f, l, c))))
{
let full_path = std::fs::canonicalize(file).unwrap_or_else(|_| {
let manifest_dir = std::path::Path::new(&manifest_path)
.parent()
.unwrap_or_else(|| {
eprintln!(
"Failed to determine parent directory for manifest: {:?}",
manifest_path
);
std::path::Path::new(".")
});
let fallback_path = manifest_dir.join(file);
std::fs::canonicalize(&fallback_path).unwrap_or_else(|_| {
let parent_fallback_path = manifest_dir.join("../").join(file);
std::fs::canonicalize(&parent_fallback_path).unwrap_or_else(|_| {
eprintln!("Failed to resolve full path for: {} using ../", file);
file.into()
})
})
});
let stripped_file = full_path.to_string_lossy().replace("\\\\?\\", "");
(stripped_file.to_string(), format!("{}:{}", line, col))
} else {
("".to_string(), "".to_string())
};
let code_path = which("code").unwrap_or_else(|_| "code".to_string().into());
format!(
"{}: {}\nanchor:{}: {}\\n {}|\"{}\" --goto \"{}:{}\"\n",
index,
diag.message.trim(),
index,
diag.message.trim(),
lineref,
code_path.display(),
filename,
goto,
)
})
.collect();
if !errors.is_empty() {
if let Ok(e_window_path) = which("e_window") {
let stats = result.stats;
let cargo_e_version = env!("CARGO_PKG_VERSION");
let card = format!(
"--title \"failed build: {target}\" --width 400 --height 300 --decode-debug\n\
target | {target} | string\n\
cargo-e | {version} | string\n\
\n\
failed build: {target}\n{errors} errors.\n\n{additional_errors}",
target = stats.target_name,
version = cargo_e_version,
errors = errors.len(),
additional_errors = line
.iter()
.map(|l| l.as_str())
.collect::<Vec<_>>()
.join("\n"),
);
let manifest_dir = std::path::Path::new(&manifest_path)
.parent()
.unwrap_or_else(|| {
eprintln!(
"Failed to determine parent directory for manifest: {:?}",
target.manifest_path
);
std::path::Path::new(".")
});
let child = std::process::Command::new(e_window_path)
.current_dir(manifest_dir) .stdin(std::process::Stdio::piped())
.spawn();
if let Ok(mut child) = child {
if let Some(stdin) = child.stdin.as_mut() {
use std::io::Write;
let _ = stdin.write_all(card.as_bytes());
if let Some(global) = crate::GLOBAL_EWINDOW_PIDS.get() {
global.insert(child.id(), child.id());
println!("[DEBUG] Added pid {} to GLOBAL_EWINDOW_PIDS", pid);
} else {
eprintln!("[DEBUG] GLOBAL_EWINDOW_PIDS is not initialized");
}
}
}
}
}
if let Some(original) = maybe_backup {
fs::write(&target.manifest_path, original)?;
}
#[cfg(feature = "uses_tts")]
wait_for_tts_to_finish(15000);
Ok(result.exit_status)
}
#[cfg(feature = "uses_tts")]
pub fn wait_for_tts_to_finish(max_wait_ms: u64) {
let tts_mutex = crate::GLOBAL_TTS.get();
if tts_mutex.is_none() {
return;
} else {
println!("Waiting for TTS to finish speaking...");
}
let start = std::time::Instant::now();
let mut tts_guard = None;
for _ in 0..3 {
if let Ok(guard) = tts_mutex.unwrap().lock() {
tts_guard = Some(guard);
break;
} else {
std::thread::sleep(std::time::Duration::from_millis(100));
}
if start.elapsed().as_millis() as u64 >= max_wait_ms {
eprintln!("Timeout while trying to lock TTS mutex.");
return;
}
}
if let Some(tts) = tts_guard {
while tts.is_speaking().unwrap_or(false) {
if start.elapsed().as_millis() as u64 >= max_wait_ms {
eprintln!("Timeout while waiting for TTS to finish speaking.");
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
} else {
eprintln!("Failed to lock TTS mutex after 3 attempts, skipping wait.");
}
}
pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
let child = Command::new("cargo").args(args).spawn()?;
Ok(child)
}
pub fn is_active_scriptisto<P: AsRef<Path>>(path: P) -> io::Result<bool> {
let file = File::open(path)?;
let mut reader = std::io::BufReader::new(file);
let mut first_line = String::new();
reader.read_line(&mut first_line)?;
if !first_line.contains("scriptisto") || !first_line.starts_with("#") {
return Ok(false);
}
Ok(true)
}
pub fn is_active_rust_script<P: AsRef<Path>>(path: P) -> io::Result<bool> {
let file = File::open(path)?;
let mut reader = std::io::BufReader::new(file);
let mut first_line = String::new();
reader.read_line(&mut first_line)?;
if !first_line.contains("rust-script") || !first_line.starts_with("#") {
return Ok(false);
}
Ok(true)
}
pub fn check_scriptisto_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
let r = which("scriptisto");
match r {
Ok(_) => {
}
Err(e) => {
eprintln!("scriptisto is not installed.");
println!("Suggestion: To install scriptisto, run the following command:");
println!("cargo install scriptisto");
return Err(e.into());
}
}
Ok(r?)
}
pub fn run_scriptisto<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
let scriptisto = check_scriptisto_installed().ok()?;
let script: &std::path::Path = script_path.as_ref();
let child = Command::new(scriptisto)
.arg(script)
.args(args)
.spawn()
.ok()?;
Some(child)
}
pub fn check_rust_script_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
let r = which("rust-script");
match r {
Ok(_) => {
}
Err(e) => {
eprintln!("rust-script is not installed.");
println!("Suggestion: To install rust-script, run the following command:");
println!("cargo install rust-script");
return Err(e.into());
}
}
Ok(r?)
}
pub fn run_rust_script<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
let rust_script = check_rust_script_installed();
if rust_script.is_err() {
return None;
}
let rust_script = rust_script.unwrap();
let script: &std::path::Path = script_path.as_ref();
let child = Command::new(rust_script)
.arg(script)
.args(args)
.spawn()
.ok()?;
Some(child)
}
pub fn run_rust_script_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
let explicit_path = Path::new(&explicit);
if explicit_path.exists() {
let extra_str_slice: Vec<String> = extra_args.iter().cloned().collect();
if let Ok(true) = is_active_rust_script(explicit_path) {
let handle = thread::spawn(move || {
let extra_str_slice_cloned = extra_str_slice.clone();
let mut child = run_rust_script(
&explicit,
&extra_str_slice_cloned
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
)
.unwrap_or_else(|| {
eprintln!("Failed to run rust-script: {:?}", &explicit);
std::process::exit(1); });
child.wait()
});
match handle.join() {
Ok(_) => {
println!("Child process finished successfully.");
}
Err(_) => {
eprintln!("Child process took too long to finish. Exiting...");
std::process::exit(1); }
}
}
}
}
pub fn run_scriptisto_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
let relative: String = make_relative(Path::new(&explicit)).unwrap_or_else(|e| {
eprintln!("Error computing relative path: {}", e);
std::process::exit(1);
});
let explicit_path = Path::new(&relative);
if explicit_path.exists() {
let extra_str_slice: Vec<String> = extra_args.to_vec();
if let Ok(true) = is_active_scriptisto(explicit_path) {
let handle = thread::spawn(move || {
let extra_str_slice_cloned: Vec<String> = extra_str_slice.clone();
let mut child = run_scriptisto(
&relative,
&extra_str_slice_cloned
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
)
.unwrap_or_else(|| {
eprintln!("Failed to run rust-script: {:?}", &explicit);
std::process::exit(1); });
let _ = child.wait();
});
match handle.join() {
Ok(_) => {
println!("Child process finished successfully.");
}
Err(_) => {
eprintln!("Child process took too long to finish. Exiting...");
std::process::exit(1); }
}
}
}
}
fn make_relative(path: &Path) -> std::io::Result<String> {
let cwd = env::current_dir()?;
let rel: PathBuf = match path.strip_prefix(&cwd) {
Ok(stripped) => stripped.to_path_buf(),
Err(_) => path.to_path_buf(),
};
let mut rel = if rel.components().count() == 0 {
PathBuf::from(".")
} else {
rel
};
let first = rel.components().next().unwrap();
match first {
std::path::Component::CurDir | std::path::Component::ParentDir => {}
_ => {
rel = PathBuf::from(".").join(rel);
}
}
let s = rel
.to_str()
.expect("Relative path should be valid UTF-8")
.to_string();
Ok(s)
}