diff --git a/src/entries.rs b/src/entries.rs index 84b9f35..68a1165 100644 --- a/src/entries.rs +++ b/src/entries.rs @@ -1,5 +1,7 @@ +use crate::context::SproutContext; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use std::rc::Rc; /// Declares a boot entry to display in the boot menu. /// @@ -8,6 +10,7 @@ use std::collections::BTreeMap; #[derive(Serialize, Deserialize, Default, Clone)] pub struct EntryDeclaration { /// The title of the entry which will be display in the boot menu. + /// This is the pre-stamped value. pub title: String, /// The actions to run when the entry is selected. #[serde(default)] @@ -16,3 +19,64 @@ pub struct EntryDeclaration { #[serde(default)] pub values: BTreeMap, } + +/// Represents an entry that is stamped and ready to be booted. +#[derive(Clone)] +pub struct BootableEntry { + name: String, + title: String, + context: Rc, + declaration: EntryDeclaration, +} + +impl BootableEntry { + /// Create a new bootable entry to represent the full context of an entry. + pub fn new( + name: String, + title: String, + context: Rc, + declaration: EntryDeclaration, + ) -> Self { + Self { + name, + title, + context, + declaration, + } + } + + /// Fetch the name of the entry. This is usually a machine-identifiable key. + pub fn name(&self) -> &str { + &self.name + } + + /// Fetch the title of the entry. This is usually a human-readable key. + pub fn title(&self) -> &str { + &self.title + } + + /// Fetch the full context of the entry. + pub fn context(&self) -> Rc { + Rc::clone(&self.context) + } + + /// Fetch the declaration of the entry. + pub fn declaration(&self) -> &EntryDeclaration { + &self.declaration + } + + /// Swap out the context of the entry. + pub fn swap_context(&mut self, context: Rc) { + self.context = context; + } + + /// Restamp the title with the current context. + pub fn restamp_title(&mut self) { + self.title = self.context.stamp(&self.title); + } + + /// Prepend the name of the entry with `prefix`. + pub fn prepend_name_prefix(&mut self, prefix: &str) { + self.name.insert_str(0, prefix); + } +} diff --git a/src/generators.rs b/src/generators.rs index 037d871..2c11b1a 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1,5 +1,5 @@ use crate::context::SproutContext; -use crate::entries::EntryDeclaration; +use crate::entries::BootableEntry; use crate::generators::bls::BlsConfiguration; use crate::generators::matrix::MatrixConfiguration; use anyhow::Result; @@ -40,7 +40,7 @@ pub struct GeneratorDeclaration { pub fn generate( context: Rc, generator: &GeneratorDeclaration, -) -> Result, EntryDeclaration)>> { +) -> Result> { if let Some(matrix) = &generator.matrix { matrix::generate(context, matrix) } else if let Some(bls) = &generator.bls { diff --git a/src/generators/bls.rs b/src/generators/bls.rs index 76075d0..cd26391 100644 --- a/src/generators/bls.rs +++ b/src/generators/bls.rs @@ -1,5 +1,5 @@ use crate::context::SproutContext; -use crate::entries::EntryDeclaration; +use crate::entries::{BootableEntry, EntryDeclaration}; use crate::generators::bls::entry::BlsEntry; use crate::utils; use anyhow::{Context, Result}; @@ -42,10 +42,7 @@ fn quirk_initrd_remove_tuned(input: String) -> String { /// Generates entries from the BLS entries directory using the specified `bls` configuration and /// `context`. The BLS conversion is best-effort and will ignore any unsupported entries. -pub fn generate( - context: Rc, - bls: &BlsConfiguration, -) -> Result, EntryDeclaration)>> { +pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result> { let mut entries = Vec::new(); // Stamp the path to the BLS entries directory. @@ -116,7 +113,7 @@ pub fn generate( // Produce a new sprout context for the entry with the extracted values. let mut context = context.fork(); - let title = entry.title().unwrap_or(name); + let title = entry.title().unwrap_or_else(|| name.clone()); let chainload = entry.chainload_path().unwrap_or_default(); let options = entry.options().unwrap_or_default(); @@ -129,7 +126,12 @@ pub fn generate( context.set("initrd", initrd); // Add the entry to the list with a frozen context. - entries.push((context.freeze(), bls.entry.clone())); + entries.push(BootableEntry::new( + name, + bls.entry.title.clone(), + context.freeze(), + bls.entry.clone(), + )); } Ok(entries) diff --git a/src/generators/matrix.rs b/src/generators/matrix.rs index c4d32f0..af21423 100644 --- a/src/generators/matrix.rs +++ b/src/generators/matrix.rs @@ -1,5 +1,5 @@ use crate::context::SproutContext; -use crate::entries::EntryDeclaration; +use crate::entries::{BootableEntry, EntryDeclaration}; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -54,13 +54,13 @@ fn build_matrix(input: &BTreeMap>) -> Vec, matrix: &MatrixConfiguration, -) -> Result, EntryDeclaration)>> { +) -> Result> { // Produce all the combinations of the input values. let combinations = build_matrix(&matrix.values); let mut entries = Vec::new(); // For each combination, create a new context and entry. - for combination in combinations { + for (index, combination) in combinations.into_iter().enumerate() { let mut context = context.fork(); // Insert the combination into the context. context.insert(&combination); @@ -68,14 +68,18 @@ pub fn generate( // Stamp the entry title and actions from the template. let mut entry = matrix.entry.clone(); - entry.title = context.stamp(&entry.title); entry.actions = entry .actions .into_iter() .map(|action| context.stamp(action)) .collect(); // Push the entry into the list with the new context. - entries.push((context, entry)); + entries.push(BootableEntry::new( + index.to_string(), + entry.title.clone(), + context, + entry, + )); } Ok(entries) diff --git a/src/main.rs b/src/main.rs index bfb5a3c..e6291f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ #![feature(uefi_std)] use crate::context::{RootContext, SproutContext}; +use crate::entries::BootableEntry; use crate::options::SproutOptions; use crate::options::parser::OptionsRepresentable; use crate::phases::phase; @@ -109,61 +110,73 @@ fn main() -> Result<()> { // Execute the late phase. phase(context.clone(), &config.phases.startup).context("unable to execute startup phase")?; - let mut staged_entries = Vec::new(); + let mut entries = Vec::new(); // Insert all the static entries from the configuration into the entry list. - for (_name, entry) in config.entries { + for (name, entry) in config.entries { // Associate the main context with the static entry. - staged_entries.push((context.clone(), entry)); + entries.push(BootableEntry::new( + name, + context.stamp(&entry.title), + context.clone(), + entry, + )); } // Run all the generators declared in the configuration. - for (_name, generator) in config.generators { + for (name, generator) in config.generators { let context = context.fork().freeze(); + // We will prefix all entries with [name]-. + let prefix = format!("{}-", name); + // Add all the entries generated by the generator to the entry list. // The generator specifies the context associated with the entry. - for entry in generators::generate(context.clone(), &generator)? { - staged_entries.push(entry); + for mut entry in generators::generate(context.clone(), &generator)? { + entry.prepend_name_prefix(&prefix); + entries.push(entry); } } - // Build a list of all the final boot entries. - let mut final_entries = Vec::new(); - for (context, entry) in staged_entries { + for entry in &mut entries { let mut context = context.fork(); // Insert the values from the entry configuration into the // sprout context to use with the entry itself. - context.insert(&entry.values); + context.insert(&entry.declaration().values); let context = context.finalize().freeze(); - - // Insert the entry configuration into final boot entries with the extended context. - final_entries.push((context, entry)); + // Provide the new context to the bootable entry. + entry.swap_context(context); + // Restamp the title with any values. + entry.restamp_title(); } // TODO(azenla): Implement boot menu here. // For now, we just print all of the entries. info!("entries:"); - for (index, (context, entry)) in final_entries.iter().enumerate() { - let title = context.stamp(&entry.title); - info!(" entry {}: {}", index + 1, title); + for (index, entry) in entries.iter().enumerate() { + let title = context.stamp(&entry.declaration().title); + info!(" entry {} [{}]: {}", index, entry.name(), title); } // Execute the late phase. phase(context.clone(), &config.phases.late).context("unable to execute late phase")?; // Use the boot option if possible, otherwise pick the first entry. - let (context, entry) = if let Some(ref boot) = context.root().options().boot { - final_entries + let entry = if let Some(ref boot) = context.root().options().boot { + entries .iter() - .find(|(_context, entry)| &entry.title == boot) + .enumerate() + .find(|(index, entry)| { + entry.name() == boot || entry.title() == boot || &index.to_string() == boot + }) .context(format!("unable to find entry: {boot}"))? + .1 // select the bootable entry. } else { - final_entries.first().context("no entries found")? + entries.first().context("no entries found")? }; // Execute all the actions for the selected entry. - for action in &entry.actions { + for action in &entry.declaration().actions { let action = context.stamp(action); actions::execute(context.clone(), &action) .context(format!("unable to execute action '{}'", action))?;