mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 13:50:16 +00:00
initial actions, entries, generators mechanism!
This commit is contained in:
@@ -1,2 +1,14 @@
|
|||||||
[[modules]]
|
version = 1
|
||||||
chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] }
|
|
||||||
|
[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 crate::utils;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use std::rc::Rc;
|
||||||
use uefi::CString16;
|
use uefi::CString16;
|
||||||
use uefi::proto::device_path::LoadedImageDevicePath;
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
||||||
use uefi::proto::loaded_image::LoadedImage;
|
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 sprout_image = uefi::boot::image_handle();
|
||||||
let image_device_path_protocol =
|
let image_device_path_protocol =
|
||||||
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
|
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
|
||||||
.expect("unable to open loaded image device path protocol");
|
.expect("unable to open loaded image device path protocol");
|
||||||
|
|
||||||
let mut full_path = utils::device_path_root(&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);
|
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)
|
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
|
||||||
.expect("unable to open loaded image protocol");
|
.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() {
|
if !options.is_empty() {
|
||||||
let options = Box::new(
|
let options = Box::new(
|
||||||
CString16::try_from(&options[..])
|
CString16::try_from(&options[..])
|
||||||
@@ -1,20 +1,68 @@
|
|||||||
use crate::utils;
|
use crate::utils;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct RootConfiguration {
|
pub struct RootConfiguration {
|
||||||
|
#[serde(default = "default_version")]
|
||||||
|
pub version: u32,
|
||||||
#[serde(default)]
|
#[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)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct ModuleConfiguration {
|
pub struct ActionDeclaration {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub chainloader: Option<ChainloaderConfiguration>,
|
pub chainload: Option<ChainloadConfiguration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct ChainloaderConfiguration {
|
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,
|
pub path: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub options: Vec<String>,
|
pub options: Vec<String>,
|
||||||
@@ -24,3 +72,7 @@ pub fn load() -> RootConfiguration {
|
|||||||
let content = utils::read_file_contents("sprout.toml");
|
let content = utils::read_file_contents("sprout.toml");
|
||||||
toml::from_slice(&content).expect("unable to parse sprout.toml file")
|
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)]
|
#![feature(uefi_std)]
|
||||||
|
|
||||||
|
use crate::config::PhaseConfiguration;
|
||||||
|
use crate::context::{Context, RootContext};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub mod actions;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod modules;
|
pub mod context;
|
||||||
|
pub mod generators;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod utils;
|
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() {
|
fn main() {
|
||||||
setup::init();
|
setup::init();
|
||||||
|
|
||||||
let config = config::load();
|
let config = config::load();
|
||||||
for module in config.modules {
|
let mut root = RootContext::new();
|
||||||
modules::execute(module);
|
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