diff --git a/local/recipes/system/cub/source/cub-lib/src/cook.rs b/local/recipes/system/cub/source/cub-lib/src/cook.rs index d4285a3b9..c62a66807 100644 --- a/local/recipes/system/cub/source/cub-lib/src/cook.rs +++ b/local/recipes/system/cub/source/cub-lib/src/cook.rs @@ -6,6 +6,13 @@ use std::process::Command; use crate::error::CubError; use crate::sandbox::SandboxConfig; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CookStrategy { + Require, + Skip, + AutoDetect, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct CookResult { pub pkgar_path: PathBuf, @@ -18,12 +25,31 @@ pub fn cook_recipe( target: &str, repo_dir: &Path, ) -> Result { - let repo_binary = find_repo_binary_with_hint(repo_dir).ok_or_else(|| { - CubError::BuildFailed( - "repo binary not found. Build tools must be installed inside Red Bear OS to cook recipes." - .to_string(), - ) - })?; + match cook_or_skip(recipe_dir, target, repo_dir, CookStrategy::Require)? { + Some(result) => Ok(result), + None => Err(CubError::BuildFailed( + "cooking was skipped despite CookStrategy::Require".to_string(), + )), + } +} + +pub fn cook_or_skip( + recipe_dir: &Path, + target: &str, + repo_dir: &Path, + strategy: CookStrategy, +) -> Result, CubError> { + let repo_binary = match strategy { + CookStrategy::Require => Some( + find_repo_binary_with_hint(repo_dir).ok_or_else(cook_unavailable_with_message_error)?, + ), + CookStrategy::Skip => return Ok(None), + CookStrategy::AutoDetect => find_repo_binary_with_hint(repo_dir), + }; + + let Some(repo_binary) = repo_binary else { + return Ok(None); + }; let mut sandbox = SandboxConfig::new(recipe_dir); sandbox.target = target.to_string(); @@ -56,11 +82,11 @@ pub fn cook_recipe( let toml_path = find_repo_artifact(repo_dir, target, &recipe_name, "toml")? .unwrap_or_else(|| recipe_dir.join("recipe.toml")); - Ok(CookResult { + Ok(Some(CookResult { pkgar_path, toml_path, stage_dir: sandbox.stage_dir, - }) + })) } pub fn is_repo_available() -> bool { @@ -83,11 +109,37 @@ pub fn cook_available() -> Result<(), CubError> { } } +pub fn cook_with_setup_instructions() -> String { + concat!( + "Build tools are not available in this environment. To cook recipes, you need:\n", + "1. The Red Bear OS build toolchain (cross-compiler for x86_64-unknown-redox)\n", + "2. The 'repo' cookbook binary in your PATH or at ./target/release/repo\n", + "\n", + "Until build tools are ported inside Red Bear OS, you can:\n", + "- Use 'cub -G ' to fetch and convert AUR packages to recipes\n", + "- Build packages on a Linux build host and transfer the .pkgar files\n", + "- Install pre-built packages directly with 'cub -S '" + ) + .to_string() +} + +pub fn cook_available_with_message() -> Result<(), CubError> { + if is_repo_available() { + Ok(()) + } else { + Err(cook_unavailable_with_message_error()) + } +} + fn find_repo_binary_with_hint(repo_dir: &Path) -> Option { let cwd = std::env::current_dir().ok(); find_repo_binary_from(std::env::var_os("PATH"), cwd.as_deref(), Some(repo_dir)) } +fn cook_unavailable_with_message_error() -> CubError { + CubError::BuildFailed(cook_with_setup_instructions()) +} + fn find_repo_binary_from( path_env: Option, cwd: Option<&Path>, @@ -244,7 +296,10 @@ fn render_command_failure(stdout: &[u8], stderr: &[u8]) -> String { #[cfg(test)] mod tests { - use super::{find_repo_binary, is_repo_available}; + use super::{ + cook_available_with_message, cook_or_skip, cook_with_setup_instructions, + find_repo_binary, is_repo_available, CookStrategy, + }; use std::fs; use std::path::{Path, PathBuf}; use std::sync::{Mutex, OnceLock}; @@ -356,6 +411,68 @@ mod tests { assert!(!is_repo_available()); } + #[test] + fn test_cook_or_skip_returns_none_with_skip_strategy() { + let _guard = env_lock().lock().expect("lock env"); + let temp = tempdir().expect("tempdir"); + let cwd = temp.path().join("project"); + fs::create_dir_all(&cwd).expect("create cwd dir"); + + let _env = TestEnvGuard::new(None, &cwd); + + let result = cook_or_skip( + &temp.path().join("recipe"), + "x86_64-unknown-redox", + &temp.path().join("repo-root"), + CookStrategy::Skip, + ) + .expect("skip strategy should succeed"); + + assert_eq!(result, None); + } + + #[test] + fn test_cook_or_skip_returns_none_with_autodetect_when_repo_missing() { + let _guard = env_lock().lock().expect("lock env"); + let temp = tempdir().expect("tempdir"); + let cwd = temp.path().join("project"); + fs::create_dir_all(&cwd).expect("create cwd dir"); + + let _env = TestEnvGuard::new(None, &cwd); + + let result = cook_or_skip( + &temp.path().join("recipe"), + "x86_64-unknown-redox", + &temp.path().join("repo-root"), + CookStrategy::AutoDetect, + ) + .expect("autodetect strategy should succeed when repo is missing"); + + assert_eq!(result, None); + } + + #[test] + fn test_cook_available_with_message_returns_instructions() { + let _guard = env_lock().lock().expect("lock env"); + let temp = tempdir().expect("tempdir"); + let cwd = temp.path().join("project"); + fs::create_dir_all(&cwd).expect("create cwd dir"); + + let _env = TestEnvGuard::new(None, &cwd); + + let error = cook_available_with_message().expect_err("repo should be unavailable"); + let message = error.to_string(); + + assert!(message.contains("Build tools are not available in this environment")); + assert!(message.contains("The Red Bear OS build toolchain")); + assert!(message.contains("'repo' cookbook binary")); + } + + #[test] + fn test_cook_with_setup_instructions_is_nonempty() { + assert!(!cook_with_setup_instructions().trim().is_empty()); + } + fn create_executable(path: &Path) { fs::write(path, b"#!/bin/sh\nexit 0\n").expect("write repo binary");