initial actions, entries, generators mechanism!

This commit is contained in:
2025-10-04 23:12:01 -07:00
parent b995ea0625
commit 24a9e2c727
10 changed files with 373 additions and 32 deletions

View File

@@ -1,2 +1,14 @@
[[modules]]
chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] }
version = 1
[values]
default-options = "tty=hvc0"
[actions.chainload-kernel]
chainload.path = "$path"
chainload.options = ["$default-options"]
[generators.kernels.matrix]
entry.title = "Boot Kernel $name"
entry.values.path = "\\EFI\\BOOT\\$name"
entry.actions = ["chainload-kernel"]
values.name = ["kernel.efi"]

View File

@@ -1,5 +0,0 @@
[[modules]]
chainloader = { path = "\\EFI\\BOOT\\SHELL.EFI" }
[[modules]]
chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] }

15
src/actions.rs Normal file
View File

@@ -0,0 +1,15 @@
use crate::config::ActionDeclaration;
use crate::context::Context;
use std::rc::Rc;
pub mod chainload;
pub fn execute(context: Rc<Context>, action: &ActionDeclaration) {
let context = context.finalize().freeze();
if let Some(chainload) = &action.chainload {
chainload::chainload(context, chainload);
} else {
panic!("unknown action configuration");
}
}

View File

@@ -1,18 +1,21 @@
use crate::config::ChainloaderConfiguration;
use crate::config::ChainloadConfiguration;
use crate::context::Context;
use crate::utils;
use log::info;
use std::rc::Rc;
use uefi::CString16;
use uefi::proto::device_path::LoadedImageDevicePath;
use uefi::proto::loaded_image::LoadedImage;
pub fn chainloader(configuration: ChainloaderConfiguration) {
pub fn chainload(context: Rc<Context>, configuration: &ChainloadConfiguration) {
let sprout_image = uefi::boot::image_handle();
let image_device_path_protocol =
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
.expect("unable to open loaded image device path protocol");
let mut full_path = utils::device_path_root(&image_device_path_protocol);
full_path.push_str(&configuration.path);
full_path.push_str(&context.stamp(&configuration.path));
info!("path={}", full_path);
@@ -30,7 +33,12 @@ pub fn chainloader(configuration: ChainloaderConfiguration) {
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
.expect("unable to open loaded image protocol");
let options = configuration.options.join(" ");
let options = configuration
.options
.iter()
.map(|item| context.stamp(item))
.collect::<Vec<_>>()
.join(" ");
if !options.is_empty() {
let options = Box::new(
CString16::try_from(&options[..])

View File

@@ -1,20 +1,68 @@
use crate::utils;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Serialize, Deserialize, Default)]
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct RootConfiguration {
#[serde(default = "default_version")]
pub version: u32,
#[serde(default)]
pub modules: Vec<ModuleConfiguration>,
pub values: BTreeMap<String, String>,
#[serde(default)]
pub actions: BTreeMap<String, ActionDeclaration>,
#[serde(default)]
pub entries: BTreeMap<String, EntryDeclaration>,
#[serde(default)]
pub generators: BTreeMap<String, GeneratorDeclaration>,
#[serde(default)]
pub phases: PhasesConfiguration,
}
#[derive(Serialize, Deserialize, Default)]
pub struct ModuleConfiguration {
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct ActionDeclaration {
#[serde(default)]
pub chainloader: Option<ChainloaderConfiguration>,
pub chainload: Option<ChainloadConfiguration>,
}
#[derive(Serialize, Deserialize, Default)]
pub struct ChainloaderConfiguration {
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct EntryDeclaration {
pub title: String,
#[serde(default)]
pub actions: Vec<String>,
#[serde(default)]
pub values: BTreeMap<String, String>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct GeneratorDeclaration {
#[serde(default)]
pub matrix: Option<MatrixConfiguration>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct PhasesConfiguration {
#[serde(default)]
pub startup: Vec<PhaseConfiguration>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct PhaseConfiguration {
#[serde(default)]
pub actions: Vec<String>,
#[serde(default)]
pub values: BTreeMap<String, String>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct MatrixConfiguration {
#[serde(default)]
pub entry: EntryDeclaration,
#[serde(default)]
pub values: BTreeMap<String, Vec<String>>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct ChainloadConfiguration {
pub path: String,
#[serde(default)]
pub options: Vec<String>,
@@ -24,3 +72,7 @@ pub fn load() -> RootConfiguration {
let content = utils::read_file_contents("sprout.toml");
toml::from_slice(&content).expect("unable to parse sprout.toml file")
}
pub fn default_version() -> u32 {
1
}

146
src/context.rs Normal file
View File

@@ -0,0 +1,146 @@
use crate::config::{ActionDeclaration, EntryDeclaration};
use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;
#[derive(Default)]
pub struct RootContext {
actions: BTreeMap<String, ActionDeclaration>,
entries: BTreeMap<String, (Rc<Context>, EntryDeclaration)>,
}
impl RootContext {
pub fn new() -> Self {
Default::default()
}
pub fn actions(&self) -> &BTreeMap<String, ActionDeclaration> {
&self.actions
}
pub fn actions_mut(&mut self) -> &mut BTreeMap<String, ActionDeclaration> {
&mut self.actions
}
pub fn entries(&self) -> &BTreeMap<String, (Rc<Context>, EntryDeclaration)> {
&self.entries
}
pub fn entries_mut(&mut self) -> &mut BTreeMap<String, (Rc<Context>, EntryDeclaration)> {
&mut self.entries
}
}
pub struct Context {
root: Rc<RootContext>,
parent: Option<Rc<Context>>,
values: BTreeMap<String, String>,
}
impl Context {
pub fn new(root: RootContext) -> Self {
Self {
root: Rc::new(root),
parent: None,
values: BTreeMap::new(),
}
}
pub fn root(&self) -> &RootContext {
self.root.as_ref()
}
pub fn get(&self, key: impl AsRef<str>) -> Option<&String> {
self.values.get(key.as_ref()).or_else(|| {
self.parent
.as_ref()
.and_then(|parent| parent.get(key.as_ref()))
})
}
pub fn all_keys(&self) -> Vec<String> {
let mut keys = BTreeSet::new();
for key in self.values.keys() {
keys.insert(key.clone());
}
if let Some(parent) = &self.parent {
keys.extend(parent.all_keys());
}
keys.into_iter().collect()
}
pub fn all_values(&self) -> BTreeMap<String, String> {
let mut values = BTreeMap::new();
for key in self.all_keys() {
values.insert(key.clone(), self.get(key).cloned().unwrap_or_default());
}
values
}
pub fn set(&mut self, key: impl AsRef<str>, value: impl ToString) {
self.values
.insert(key.as_ref().to_string(), value.to_string());
}
pub fn insert(&mut self, values: &BTreeMap<String, String>) {
for (key, value) in values {
self.values.insert(key.clone(), value.clone());
}
}
pub fn fork(self: &Rc<Context>) -> Self {
Self {
root: self.root.clone(),
parent: Some(self.clone()),
values: BTreeMap::new(),
}
}
pub fn freeze(self) -> Rc<Context> {
Rc::new(self)
}
pub fn finalize(&self) -> Context {
let mut current_values = self.all_values();
loop {
let mut did_change = false;
let mut values = BTreeMap::new();
for (key, value) in &current_values {
let (changed, result) = Self::stamp_values(&current_values, value);
if changed {
did_change = true;
}
values.insert(key.clone(), result);
}
current_values = values;
if !did_change {
break;
}
}
Self {
root: self.root.clone(),
parent: None,
values: current_values,
}
}
fn stamp_values(values: &BTreeMap<String, String>, text: impl AsRef<str>) -> (bool, String) {
let mut result = text.as_ref().to_string();
let mut did_change = false;
for (key, value) in values {
let next_result = result.replace(&format!("${key}"), value);
if result != next_result {
did_change = true;
}
result = next_result;
}
(did_change, result)
}
pub fn stamp(&self, text: impl AsRef<str>) -> String {
Self::stamp_values(&self.all_values(), text.as_ref()).1
}
}

16
src/generators.rs Normal file
View File

@@ -0,0 +1,16 @@
use crate::config::{EntryDeclaration, GeneratorDeclaration};
use crate::context::Context;
use std::rc::Rc;
pub mod matrix;
pub fn generate(
context: Rc<Context>,
generator: &GeneratorDeclaration,
) -> Vec<(Rc<Context>, EntryDeclaration)> {
if let Some(matrix) = &generator.matrix {
matrix::generate(context, matrix)
} else {
panic!("unknown action configuration");
}
}

50
src/generators/matrix.rs Normal file
View File

@@ -0,0 +1,50 @@
use crate::config::{EntryDeclaration, MatrixConfiguration};
use crate::context::Context;
use std::collections::BTreeMap;
use std::rc::Rc;
fn build_matrix(input: &BTreeMap<String, Vec<String>>) -> Vec<BTreeMap<String, String>> {
let items: Vec<(String, Vec<String>)> = input.clone().into_iter().collect();
let mut result: Vec<BTreeMap<String, String>> = vec![BTreeMap::new()];
for (key, values) in items {
let mut new_result = Vec::new();
for combination in &result {
for value in &values {
let mut new_combination = combination.clone();
new_combination.insert(key.clone(), value.clone());
new_result.push(new_combination);
}
}
result = new_result;
}
result.into_iter().filter(|item| !item.is_empty()).collect()
}
pub fn generate(
context: Rc<Context>,
matrix: &MatrixConfiguration,
) -> Vec<(Rc<Context>, EntryDeclaration)> {
let combinations = build_matrix(&matrix.values);
let mut entries = Vec::new();
for combination in combinations {
let mut context = context.fork();
context.insert(&combination);
let context = context.freeze();
let mut entry = matrix.entry.clone();
entry.title = context.stamp(&entry.title);
entry.actions = entry
.actions
.into_iter()
.map(|action| context.stamp(action))
.collect();
entries.push((context, entry));
}
entries
}

View File

@@ -1,14 +1,72 @@
#![feature(uefi_std)]
use crate::config::PhaseConfiguration;
use crate::context::{Context, RootContext};
use std::rc::Rc;
pub mod actions;
pub mod config;
pub mod modules;
pub mod context;
pub mod generators;
pub mod setup;
pub mod utils;
fn phase(context: Rc<Context>, phase: &[PhaseConfiguration]) {
for item in phase {
let mut context = context.fork();
context.insert(&item.values);
let context = context.freeze();
for action in item.actions.iter() {
let Some(action) = context.root().actions().get(action) else {
panic!("unknown action: {}", action);
};
actions::execute(context.clone(), action);
}
}
}
fn main() {
setup::init();
let config = config::load();
for module in config.modules {
modules::execute(module);
let mut root = RootContext::new();
root.actions_mut().extend(config.actions.clone());
let mut context = Context::new(root);
context.insert(&config.values);
let context = context.freeze();
phase(context.clone(), &config.phases.startup);
let mut all_entries = Vec::new();
for (_name, entry) in config.entries {
all_entries.push((context.clone(), entry));
}
for (_name, generator) in config.generators {
let context = context.fork().freeze();
for entry in generators::generate(context.clone(), &generator) {
all_entries.push(entry);
}
}
println!("{} entries", all_entries.len());
for (index, (context, entry)) in all_entries.iter().enumerate() {
let mut context = context.fork();
context.insert(&entry.values);
let context = context.finalize().freeze();
println!("Entry {}:", index + 1);
println!(" Title: {}", entry.title);
println!(" Actions: {:?}", entry.actions);
println!(" Values: {:?}", context.all_values());
}
loop {
std::thread::sleep(std::time::Duration::from_secs(5));
}
}

View File

@@ -1,11 +0,0 @@
use crate::config::ModuleConfiguration;
pub mod chainloader;
pub fn execute(module: ModuleConfiguration) {
if let Some(chainloader) = module.chainloader {
chainloader::chainloader(chainloader);
} else {
panic!("unknown module configuration");
}
}