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::error::CubError;
|
||||||
use crate::sandbox::SandboxConfig;
|
use crate::sandbox::SandboxConfig;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum CookStrategy {
|
||||||
|
Require,
|
||||||
|
Skip,
|
||||||
|
AutoDetect,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct CookResult {
|
pub struct CookResult {
|
||||||
pub pkgar_path: PathBuf,
|
pub pkgar_path: PathBuf,
|
||||||
@@ -18,12 +25,31 @@ pub fn cook_recipe(
|
|||||||
target: &str,
|
target: &str,
|
||||||
repo_dir: &Path,
|
repo_dir: &Path,
|
||||||
) -> Result<CookResult, CubError> {
|
) -> Result<CookResult, CubError> {
|
||||||
let repo_binary = find_repo_binary_with_hint(repo_dir).ok_or_else(|| {
|
match cook_or_skip(recipe_dir, target, repo_dir, CookStrategy::Require)? {
|
||||||
CubError::BuildFailed(
|
Some(result) => Ok(result),
|
||||||
"repo binary not found. Build tools must be installed inside Red Bear OS to cook recipes."
|
None => Err(CubError::BuildFailed(
|
||||||
.to_string(),
|
"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);
|
let mut sandbox = SandboxConfig::new(recipe_dir);
|
||||||
sandbox.target = target.to_string();
|
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")?
|
let toml_path = find_repo_artifact(repo_dir, target, &recipe_name, "toml")?
|
||||||
.unwrap_or_else(|| recipe_dir.join("recipe.toml"));
|
.unwrap_or_else(|| recipe_dir.join("recipe.toml"));
|
||||||
|
|
||||||
Ok(CookResult {
|
Ok(Some(CookResult {
|
||||||
pkgar_path,
|
pkgar_path,
|
||||||
toml_path,
|
toml_path,
|
||||||
stage_dir: sandbox.stage_dir,
|
stage_dir: sandbox.stage_dir,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_repo_available() -> bool {
|
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> {
|
fn find_repo_binary_with_hint(repo_dir: &Path) -> Option<PathBuf> {
|
||||||
let cwd = std::env::current_dir().ok();
|
let cwd = std::env::current_dir().ok();
|
||||||
find_repo_binary_from(std::env::var_os("PATH"), cwd.as_deref(), Some(repo_dir))
|
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(
|
fn find_repo_binary_from(
|
||||||
path_env: Option<OsString>,
|
path_env: Option<OsString>,
|
||||||
cwd: Option<&Path>,
|
cwd: Option<&Path>,
|
||||||
@@ -244,7 +296,10 @@ fn render_command_failure(stdout: &[u8], stderr: &[u8]) -> String {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
@@ -356,6 +411,68 @@ mod tests {
|
|||||||
assert!(!is_repo_available());
|
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) {
|
fn create_executable(path: &Path) {
|
||||||
fs::write(path, b"#!/bin/sh\nexit 0\n").expect("write repo binary");
|
fs::write(path, b"#!/bin/sh\nexit 0\n").expect("write repo binary");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user