mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 15:40:16 +00:00
initial actions, entries, generators mechanism!
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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
15
src/actions.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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[..])
|
||||
@@ -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
146
src/context.rs
Normal 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 ¤t_values {
|
||||
let (changed, result) = Self::stamp_values(¤t_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
16
src/generators.rs
Normal 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
50
src/generators/matrix.rs
Normal 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
|
||||
}
|
||||
64
src/main.rs
64
src/main.rs
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user