Files
honey-biscuit-workshop/workshop-baker/src/bake.rs
T
Catty Steve 2658d0ebfa feat(baker): add build status tracking and notification dispatch system
Add new `build_status` types module for tracking pipeline stage
execution
status across prebake, bake, and finalize phases. Introduce
`NotificationCore`
dispatcher that processes notification methods by priority groups with
fallback
on failure. Status files written to `/tmp/.hbwstatus` in JSON lines
format.
2026-04-22 18:45:22 +08:00

159 lines
5.1 KiB
Rust

use crate::bake::parser::Function;
use crate::cli::Cli;
use crate::constant::{DEFAULT_WORKSPACE, STANDALONE_STATUS_DIR, TRIVIAL_SCRIPT_NAME};
use crate::engine::EventSender;
use crate::error::BakeError;
use crate::ExecutionContext;
use crate::prebake::PrebakeConfig;
use crate::prebake::security::get_drop_after;
use crate::prebake::stage::PrebakeStage;
use crate::types::build_status::{write_stage_status, StageInfo, StagePhase, StageResult};
use crate::{Engine, prebake};
use std::path::{PathBuf, Path};
use chrono::Utc;
mod builder;
mod decorator;
pub mod parser;
mod schedule;
pub async fn bake(
script_path: &Path,
bake_base_path: &Path,
prebake_path: &Path,
cli: &Cli,
ctx: &mut ExecutionContext,
event_tx: EventSender,
) -> Result<(), BakeError> {
let script_content = std::fs::read_to_string(script_path)?;
let prebake_content = std::fs::read_to_string(prebake_path)?;
let prebake = prebake::parse(prebake_content.as_str()).map_err(|e| {
log::error!("Failed to parse prebake.yml: {}", e);
log::error!("This is unexpected!");
BakeError::YamlParseError(e)
})?;
let workspace = get_workspace(&prebake);
let functions = parser::parse_script(&script_content)?;
let has_pipeline = functions.pipeline_functions.iter().any(|f| {
f.decorators
.iter()
.any(|d| matches!(d, decorator::Decorator::Pipeline))
});
let trivial = false;
let use_template = true;
let engine = Engine::new();
if let Some(env) = &prebake.envvars {
if let Some(prebake_env) = &env.bake {
ctx.env_vars.extend(prebake_env.clone());
}
}
if !has_pipeline || trivial {
let stage_start = Utc::now();
let function = Function{
name: TRIVIAL_SCRIPT_NAME.to_string(),
decorators: vec![],
body: script_content,
};
let script = builder::build_script(
&function,
bake_base_path,
&workspace,
None,
use_template,
)?;
let result = engine.execute_script(&script, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
let _ = write_stage_status(
STANDALONE_STATUS_DIR,
StageInfo {
name: "trivial".to_string(),
phase: StagePhase::Bake,
sub_stage: "trivial".to_string(),
result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
started_at: stage_start,
finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()),
},
);
result?;
} else {
let sorted_functions = schedule::sort_function(functions.pipeline_functions)?;
let remaining = functions.remaining_code;
for func in sorted_functions {
let stage_start = Utc::now();
let mut modified_func = func.clone();
modified_func.body = remaining.clone() + &func.body;
let script = builder::build_script(
&modified_func,
bake_base_path,
&workspace,
None,
use_template,
)?;
let result = engine.execute_script(&script, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
let _ = write_stage_status(
STANDALONE_STATUS_DIR,
StageInfo {
name: func.name.clone(),
phase: StagePhase::Bake,
sub_stage: func.name.clone(),
result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
started_at: stage_start,
finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()),
},
);
result?;
}
}
Ok(())
}
fn get_workspace(prebake_config: &PrebakeConfig) -> PathBuf {
prebake_config
.bootstrap
.as_ref()
.and_then(|b| b.workspace.as_ref())
.map(|ws| {
if ws.fallback {
PathBuf::from(DEFAULT_WORKSPACE)
} else {
PathBuf::from(ws.path.clone())
}
})
.unwrap_or_else(|| PathBuf::from(DEFAULT_WORKSPACE))
}
fn privileged(prebake_config: &PrebakeConfig) -> bool {
if let Some(bootstrap) = &prebake_config.bootstrap {
if bootstrap.user == "root" {
return true;
}
}
let drop_after = get_drop_after(&prebake_config.security).unwrap_or_default();
PrebakeStage::Never == drop_after
}
type TaskResult = Result<(), BakeError>;
type TaskFn = Box<dyn FnOnce() -> TaskResult + Send>;
// TODO: Learn closure and continue
// async fn run<F>(f:F) -> TaskFn {
// let engine = Engine::new();
// engine.execute_script
// }
// fn with_timeout<F>(f: F, duration: i32) -> TaskFn {
// }