feat: add Cub cook autodetect strategy
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -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<CookResult, CubError> {
|
||||
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<Option<CookResult>, 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 <pkg>' 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 <pkg>'"
|
||||
)
|
||||
.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<PathBuf> {
|
||||
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<OsString>,
|
||||
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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user