From a70aa556f39ff49bbe34ba8de08a4caba526ff09 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Thu, 11 Sep 2025 11:48:39 -0700 Subject: [PATCH 1/5] setup: add options & fix foundry (on linux) --- src/main.rs | 41 ++++++++++++++++++++++++++++++++++- src/setup/mod.rs | 56 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 50957dd2..e8226cad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -468,9 +468,20 @@ 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 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, + *verbose, + ).await } Some(("start-package", matches)) => { let package_dir = PathBuf::from(matches.get_one::("DIR").unwrap()); @@ -1249,6 +1260,34 @@ 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) + ) ) .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..5cd20ad1 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)?; @@ -466,13 +473,48 @@ pub async fn get_deps( } #[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, + 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()?); + 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 mut docker_deps = check_docker_deps()?; + if !docker_optional { + missing_deps.append(&mut docker_deps); + } else { + warn!("Docker deps are not satisfied: {docker_deps:?}"); + } + + 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, verbose).await?; info!("Done setting up."); From f33e4bebea20d9b0717be8ae336d13462259bfa1 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Thu, 11 Sep 2025 12:54:41 -0700 Subject: [PATCH 2/5] setup: fix it --- src/setup/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/setup/mod.rs b/src/setup/mod.rs index 5cd20ad1..52afb7cf 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -501,11 +501,13 @@ pub async fn execute( warn!("JavaScript deps are not satisfied: {js_deps:?}"); } - let mut docker_deps = check_docker_deps()?; + let docker_result = check_docker_deps(); if !docker_optional { - missing_deps.append(&mut docker_deps); + missing_deps.append(&mut docker_result?); } else { - warn!("Docker deps are not satisfied: {docker_deps:?}"); + if let Err(e) = docker_result { + warn!("Docker deps are not satisfied: {e}"); + } } let mut foundry_deps = check_foundry_deps()?; From c4b896495e9776d28b7c551bcc29e80837b3ceeb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:55:10 +0000 Subject: [PATCH 3/5] Format Rust code using rustfmt --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index e8226cad..b89711e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -481,7 +481,8 @@ async fn execute( *foundry_optional, *javascript_optional, *verbose, - ).await + ) + .await } Some(("start-package", matches)) => { let package_dir = PathBuf::from(matches.get_one::("DIR").unwrap()); From 64286d6b876a57fe6efdd0b4d609e5ec3ee58eaf Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Thu, 11 Sep 2025 13:21:53 -0700 Subject: [PATCH 4/5] setup: add --non-interactive mode --- src/build/mod.rs | 6 +-- src/chain/mod.rs | 2 +- src/dev_ui/mod.rs | 2 +- src/main.rs | 8 ++++ src/setup/mod.rs | 120 +++++++++++++++++++++++++--------------------- 5 files changed, 78 insertions(+), 60 deletions(-) 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 b89711e6..6d363f75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -472,6 +472,7 @@ async fn execute( 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( @@ -480,6 +481,7 @@ async fn execute( *python_optional, *foundry_optional, *javascript_optional, + *non_interactive, *verbose, ) .await @@ -1289,6 +1291,12 @@ async fn make_app(current_dir: &std::ffi::OsString) -> Result { .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 52afb7cf..4d7fd464 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -404,70 +404,79 @@ 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(()) } @@ -479,6 +488,7 @@ pub async fn execute( python_optional: bool, foundry_optional: bool, javascript_optional: bool, + non_interactive: bool, verbose: bool, ) -> Result<()> { info!("Setting up..."); @@ -517,7 +527,7 @@ pub async fn execute( warn!("Foundry deps are not satisfied: {foundry_deps:?}"); } - get_deps(missing_deps, recv_kill, verbose).await?; + get_deps(missing_deps, recv_kill, non_interactive, verbose).await?; info!("Done setting up."); Ok(()) From 99124a1634f091a0a8d08c68f60773a77c326162 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Thu, 11 Sep 2025 13:27:21 -0700 Subject: [PATCH 5/5] setup: fix boolean logic switcheroo --- src/setup/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup/mod.rs b/src/setup/mod.rs index 4d7fd464..db09cd25 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -411,7 +411,7 @@ pub async fn get_deps( return Ok(()); } - if !non_interactive { + if non_interactive { install_deps(deps, verbose)?; } else { // If setup required, request user permission