diff --git a/src/build/mod.rs b/src/build/mod.rs index bd2c0967..d7f5502b 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -1469,14 +1469,14 @@ async fn check_and_populate_dependencies( if path.is_dir() { if path.join(RUST_SRC_PATH).exists() && !checked_rust && !skip_deps_check { let deps = check_rust_deps()?; - get_deps(deps, &mut recv_kill, verbose).await?; + get_deps(deps, &mut recv_kill, false, verbose).await?; checked_rust = true; } else if path.join(PYTHON_SRC_PATH).exists() && !checked_py { check_py_deps()?; checked_py = true; } else if path.join(JAVASCRIPT_SRC_PATH).exists() && !checked_js && !skip_deps_check { let deps = check_js_deps()?; - get_deps(deps, &mut recv_kill, verbose).await?; + get_deps(deps, &mut recv_kill, false, verbose).await?; checked_js = true; } else if Some("api") == path.file_name().and_then(|s| s.to_str()) { // read api files: to be used in build @@ -1859,7 +1859,7 @@ pub async fn execute( if !skip_deps_check { let mut recv_kill = make_fake_kill_chan(); let deps = check_js_deps()?; - get_deps(deps, &mut recv_kill, verbose).await?; + get_deps(deps, &mut recv_kill, false, verbose).await?; } let valid_node = get_newest_valid_node_version(None, None)?; for ui_dir in ui_dirs { diff --git a/src/chain/mod.rs b/src/chain/mod.rs index e9120a49..ce55240a 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -257,7 +257,7 @@ pub async fn start_chain( tracing: bool, ) -> Result> { let deps = check_foundry_deps()?; - get_deps(deps, &mut recv_kill, verbose).await?; + get_deps(deps, &mut recv_kill, false, verbose).await?; info!("Checking for Anvil on port {}...", port); if wait_for_anvil(port, 1, None).await.is_ok() { diff --git a/src/dev_ui/mod.rs b/src/dev_ui/mod.rs index d8bff231..5d2cbe44 100644 --- a/src/dev_ui/mod.rs +++ b/src/dev_ui/mod.rs @@ -17,7 +17,7 @@ pub async fn execute( if !skip_deps_check { let deps = check_js_deps()?; let mut recv_kill = make_fake_kill_chan(); - get_deps(deps, &mut recv_kill, false).await?; + get_deps(deps, &mut recv_kill, false, false).await?; } let valid_node = get_newest_valid_node_version(None, None)?; diff --git a/src/main.rs b/src/main.rs index 50957dd2..6d363f75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -468,9 +468,23 @@ async fn execute( } Some(("setup", matches)) => { let verbose = matches.get_one::("VERBOSE").unwrap(); + let docker_optional = matches.get_one::("DOCKER_OPTIONAL").unwrap(); + let python_optional = matches.get_one::("PYTHON_OPTIONAL").unwrap(); + let foundry_optional = matches.get_one::("FOUNDRY_OPTIONAL").unwrap(); + let javascript_optional = matches.get_one::("JAVASCRIPT_OPTIONAL").unwrap(); + let non_interactive = matches.get_one::("NON_INTERACTIVE").unwrap(); let mut recv_kill = build::make_fake_kill_chan(); - setup::execute(&mut recv_kill, *verbose).await + setup::execute( + &mut recv_kill, + *docker_optional, + *python_optional, + *foundry_optional, + *javascript_optional, + *non_interactive, + *verbose, + ) + .await } Some(("start-package", matches)) => { let package_dir = PathBuf::from(matches.get_one::("DIR").unwrap()); @@ -1249,6 +1263,40 @@ async fn make_app(current_dir: &std::ffi::OsString) -> Result { .help("If set, output stdout and stderr") .required(false) ) + .arg(Arg::new("DOCKER_OPTIONAL") + .action(ArgAction::SetTrue) + .short('d') + .long("docker-optional") + .help("If set, don't require Docker dep (just warn if missing)") + .required(false) + ) + .arg(Arg::new("PYTHON_OPTIONAL") + .action(ArgAction::SetTrue) + .short('p') + .long("python-optional") + .help("If set, don't require Python dep (just warn if missing)") + .required(false) + ) + .arg(Arg::new("FOUNDRY_OPTIONAL") + .action(ArgAction::SetTrue) + .short('f') + .long("foundry-optional") + .help("If set, don't require Foundry dep (just warn if missing)") + .required(false) + ) + .arg(Arg::new("JAVASCRIPT_OPTIONAL") + .action(ArgAction::SetTrue) + .short('j') + .long("javascript-optional") + .help("If set, don't require Javascript deps (just warn if missing)") + .required(false) + ) + .arg(Arg::new("NON_INTERACTIVE") + .action(ArgAction::SetTrue) + .long("non-interactive") + .help("If set, do not prompt and instead always reply `Y` to prompts (i.e. automatically install dependencies without user input)") + .required(false) + ) ) .subcommand(Command::new("start-package") .about("Start a built Hyprware package") diff --git a/src/setup/mod.rs b/src/setup/mod.rs index 16cba2a0..db09cd25 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -48,6 +48,13 @@ impl std::fmt::Display for Dependency { } } +// use Display +impl std::fmt::Debug for Dependency { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self) + } +} + // hack to allow definition of Display struct Dependencies(Vec); impl std::fmt::Display for Dependencies { @@ -347,11 +354,11 @@ pub fn check_foundry_deps() -> Result> { Ok(vec![]) } -/// install forge+anvil+others, could be separated into binary extractions from github releases. +/// install Foundry, could be separated into binary extractions from github releases. #[instrument(level = "trace", skip_all)] fn install_foundry(verbose: bool) -> Result<()> { let download_cmd = "curl -L https://foundry.paradigm.xyz | bash"; - let install_cmd = ". ~/.bashrc && foundryup"; + let install_cmd = "export PATH=\"$PATH:$HOME/.foundry/bin\" && foundryup"; run_command(Command::new("bash").args(&["-c", download_cmd]), verbose)?; run_command(Command::new("bash").args(&["-c", install_cmd]), verbose)?; @@ -397,83 +404,130 @@ pub fn check_docker_deps() -> Result> { pub async fn get_deps( deps: Vec, recv_kill: &mut BroadcastRecvBool, + non_interactive: bool, verbose: bool, ) -> Result<()> { if deps.is_empty() { return Ok(()); } - // If setup required, request user permission - print!( - "kit requires {} missing {}: {}. Install? [Y/n]: ", - if deps.len() == 1 { "this" } else { "these" }, - if deps.len() == 1 { - "dependency" - } else { - "dependencies" - }, - Dependencies(deps.clone()), - ); - // Flush to ensure the prompt is displayed before input - io::stdout().flush().unwrap(); - - // Read the user's response - let (sender, mut receiver) = tokio::sync::mpsc::channel(1); - tokio::spawn(async move { - let mut response = String::new(); - io::stdin().read_line(&mut response).unwrap(); - sender.send(response).await.unwrap(); - }); - - // Process the response - let response = tokio::select! { - Some(response) = receiver.recv() => response, - k = recv_kill.recv() => { - match k { - Err(tokio::sync::broadcast::error::RecvError::Closed) => { - // some systems drop the fake sender produced in build/mod.rs:57 - // make_fake_kill_chan() and so we handle this by ignoring the - // Closed message that comes through - // https://docs.rs/tokio/latest/tokio/sync/broadcast/struct.Receiver.html#method.recv - receiver.recv().await.unwrap() + if non_interactive { + install_deps(deps, verbose)?; + } else { + // If setup required, request user permission + print!( + "kit requires {} missing {}: {}. Install? [Y/n]: ", + if deps.len() == 1 { "this" } else { "these" }, + if deps.len() == 1 { + "dependency" + } else { + "dependencies" + }, + Dependencies(deps.clone()), + ); + // Flush to ensure the prompt is displayed before input + io::stdout().flush().unwrap(); + + // Read the user's response + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + tokio::spawn(async move { + let mut response = String::new(); + io::stdin().read_line(&mut response).unwrap(); + sender.send(response).await.unwrap(); + }); + + // Process the response + let response = tokio::select! { + Some(response) = receiver.recv() => response, + k = recv_kill.recv() => { + match k { + Err(tokio::sync::broadcast::error::RecvError::Closed) => { + // some systems drop the fake sender produced in build/mod.rs:57 + // make_fake_kill_chan() and so we handle this by ignoring the + // Closed message that comes through + // https://docs.rs/tokio/latest/tokio/sync/broadcast/struct.Receiver.html#method.recv + receiver.recv().await.unwrap() + } + _ => return Err(eyre!("got exit code")), } - _ => return Err(eyre!("got exit code")), } + }; + let response = response.trim().to_lowercase(); + match response.as_str() { + "y" | "yes" | "" => install_deps(deps, verbose)?, + r => warn!("Got '{}'; not getting deps.", r), } - }; - let response = response.trim().to_lowercase(); - match response.as_str() { - "y" | "yes" | "" => { - for dep in deps { - match dep { - Dependency::Nvm => install_nvm(verbose)?, - Dependency::Npm => call_with_nvm(&format!("nvm install-latest-npm"), verbose)?, - Dependency::Node => call_with_nvm( - &format!("nvm install {}.{}", REQUIRED_NODE_MAJOR, MINIMUM_NODE_MINOR,), - verbose, - )?, - Dependency::Rust => install_rust(verbose)?, - Dependency::RustWasm32Wasi => call_rustup("target add wasm32-wasip1", verbose)?, - Dependency::WasmTools => call_cargo("install wasm-tools", verbose)?, - Dependency::Foundry => install_foundry(verbose)?, - Dependency::Docker => {} - } - } + } + Ok(()) +} + +#[instrument(level = "trace", skip_all)] +fn install_deps(deps: Vec, verbose: bool) -> Result<()> { + for dep in deps { + match dep { + Dependency::Nvm => install_nvm(verbose)?, + Dependency::Npm => call_with_nvm(&format!("nvm install-latest-npm"), verbose)?, + Dependency::Node => call_with_nvm( + &format!("nvm install {}.{}", REQUIRED_NODE_MAJOR, MINIMUM_NODE_MINOR,), + verbose, + )?, + Dependency::Rust => install_rust(verbose)?, + Dependency::RustWasm32Wasi => call_rustup("target add wasm32-wasip1", verbose)?, + Dependency::WasmTools => call_cargo("install wasm-tools", verbose)?, + Dependency::Foundry => install_foundry(verbose)?, + Dependency::Docker => {} } - r => warn!("Got '{}'; not getting deps.", r), } Ok(()) } #[instrument(level = "trace", skip_all)] -pub async fn execute(recv_kill: &mut BroadcastRecvBool, verbose: bool) -> Result<()> { +pub async fn execute( + recv_kill: &mut BroadcastRecvBool, + docker_optional: bool, + python_optional: bool, + foundry_optional: bool, + javascript_optional: bool, + non_interactive: bool, + verbose: bool, +) -> Result<()> { info!("Setting up..."); - check_py_deps()?; - let mut missing_deps = check_js_deps()?; - missing_deps.append(&mut check_rust_deps()?); - missing_deps.append(&mut check_docker_deps()?); - get_deps(missing_deps, recv_kill, verbose).await?; + let py_result = check_py_deps(); + if !python_optional { + py_result?; + } else { + if let Err(e) = py_result { + warn!("Python deps are not satisfied: {e}"); + } + } + + let mut missing_deps = check_rust_deps()?; + + let mut js_deps = check_js_deps()?; + if !javascript_optional { + missing_deps.append(&mut js_deps); + } else { + warn!("JavaScript deps are not satisfied: {js_deps:?}"); + } + + let docker_result = check_docker_deps(); + if !docker_optional { + missing_deps.append(&mut docker_result?); + } else { + if let Err(e) = docker_result { + warn!("Docker deps are not satisfied: {e}"); + } + } + + let mut foundry_deps = check_foundry_deps()?; + if !foundry_optional { + missing_deps.append(&mut foundry_deps); + } else { + warn!("Foundry deps are not satisfied: {foundry_deps:?}"); + } + + get_deps(missing_deps, recv_kill, non_interactive, verbose).await?; info!("Done setting up."); Ok(())