mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 22:30:17 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
354b5ec130
|
|||
|
ac918ee0ef
|
|||
|
13129d9a83
|
|||
|
4359508fd7
|
|||
|
eef4f134b5
|
|||
|
ac5c83fe46
|
|||
|
73fb21d41f
|
|||
|
ac76be5cb5
|
|||
|
37434d7f65
|
|||
|
9b8ba30f56
|
|||
|
f5f431458c
|
|||
|
1bba345dc2
|
|||
|
9f47ec42e5
|
74
.github/workflows/publish.yaml
vendored
Normal file
74
.github/workflows/publish.yaml
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
name: publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- bin/**
|
||||||
|
- src/**
|
||||||
|
- Cargo.*
|
||||||
|
- rust-toolchain.toml
|
||||||
|
- .github/workflows/publish.yaml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # Needed to checkout the repository.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
artifacts:
|
||||||
|
name: artifacts
|
||||||
|
permissions:
|
||||||
|
contents: write # Needed to upload release assets and artifacts.
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: harden runner
|
||||||
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: 'install nightly rust toolchain'
|
||||||
|
run: |
|
||||||
|
rustup update --no-self-update nightly
|
||||||
|
rustup default nightly
|
||||||
|
|
||||||
|
- name: 'assemble artifacts'
|
||||||
|
run: ./hack/assemble.sh
|
||||||
|
|
||||||
|
- name: 'upload sprout-x86_64.efi artifact'
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: sprout-x86_64.efi
|
||||||
|
path: target/assemble/sprout-x86_64.efi
|
||||||
|
|
||||||
|
- name: 'upload sprout-aarch64.efi artifact'
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: sprout-aarch64.efi
|
||||||
|
path: target/assemble/sprout-aarch64.efi
|
||||||
|
|
||||||
|
- name: 'generate cultivator token'
|
||||||
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
|
id: generate-token
|
||||||
|
with:
|
||||||
|
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
|
||||||
|
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
|
||||||
|
|
||||||
|
- name: 'upload release artifacts'
|
||||||
|
run: ./hack/ci/upload-release-assets.sh
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
|
||||||
|
RELEASE_TAG: "${{ github.event.release.tag_name }}"
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
41
.github/workflows/release-assets.yaml
vendored
41
.github/workflows/release-assets.yaml
vendored
@@ -1,41 +0,0 @@
|
|||||||
name: release assets
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- created
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # Needed to checkout the repository.
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
assets:
|
|
||||||
name: assets
|
|
||||||
permissions:
|
|
||||||
contents: write # Needed to upload release assets.
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: harden runner
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: 'install nightly rust toolchain'
|
|
||||||
run: |
|
|
||||||
rustup update --no-self-update nightly
|
|
||||||
rustup default nightly
|
|
||||||
|
|
||||||
- name: 'assemble release artifacts'
|
|
||||||
run: ./hack/assemble.sh
|
|
||||||
|
|
||||||
- name: 'upload release artifacts'
|
|
||||||
run: ./hack/ci/upload-release-assets.sh
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
RELEASE_TAG: "${{ github.event.release.tag_name }}"
|
|
||||||
if: ${{ github.event_name == 'release' }}
|
|
||||||
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -59,6 +59,19 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "edera-sprout"
|
||||||
|
version = "0.0.3"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"image",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
|
"uefi",
|
||||||
|
"uefi-raw",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -251,19 +264,6 @@ name = "simd-adler32"
|
|||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
source = "git+https://github.com/edera-dev/sprout-patched-deps.git?rev=2c4fcc84b50d40c28f540d4271109ea0ca7e1268#2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
|
source = "git+https://github.com/edera-dev/sprout-patched-deps.git?rev=2c4fcc84b50d40c28f540d4271109ea0ca7e1268#2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sprout"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"image",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"toml",
|
|
||||||
"uefi",
|
|
||||||
"uefi-raw",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
|
|||||||
20
Cargo.toml
20
Cargo.toml
@@ -1,6 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sprout"
|
name = "edera-sprout"
|
||||||
version = "0.0.2"
|
description = "Modern UEFI bootloader"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
version = "0.0.3"
|
||||||
|
homepage = "https://github.com/edera-dev/sprout"
|
||||||
|
repository = "https://github.com/edera-dev/sprout"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -25,14 +29,14 @@ features = ["alloc", "logger"]
|
|||||||
[dependencies.uefi-raw]
|
[dependencies.uefi-raw]
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = "thin"
|
|
||||||
strip = "symbols"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["splash"]
|
default = ["splash"]
|
||||||
splash = ["dep:image"]
|
splash = ["dep:image"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
strip = "symbols"
|
||||||
|
|
||||||
[profile.release-debuginfo]
|
[profile.release-debuginfo]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
strip = "none"
|
strip = "none"
|
||||||
@@ -50,3 +54,7 @@ rev = "2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
|
|||||||
[patch.crates-io.moxcms]
|
[patch.crates-io.moxcms]
|
||||||
git = "https://github.com/edera-dev/sprout-patched-deps.git"
|
git = "https://github.com/edera-dev/sprout-patched-deps.git"
|
||||||
rev = "2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
|
rev = "2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "sprout"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -1,7 +1,10 @@
|
|||||||
<p align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="assets/logo.png" alt="sprout logo" width="258" height="200" />
|

|
||||||
<h1 align="center">Sprout</h1>
|
|
||||||
|
# Sprout
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
Sprout is an **EXPERIMENTAL** programmable UEFI bootloader written in Rust.
|
Sprout is an **EXPERIMENTAL** programmable UEFI bootloader written in Rust.
|
||||||
|
|
||||||
@@ -18,7 +21,7 @@ Sprout is licensed under Apache 2.0 and is open to modifications and contributio
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
NOTE: Currently, Sprout is experimental and is not intended for production use. For example, it doesn't currently
|
NOTE: Currently, Sprout is experimental and is not intended for production use. For example, it doesn't currently
|
||||||
have secure boot support. In fact, as of writing, it doesn't even have a boot menu. Instead, it boots the first entry it sees, or panics.
|
have secure boot support. In fact, as of writing, it doesn't even have a boot menu. Instead, it boots the first entry it sees, or fails.
|
||||||
|
|
||||||
### Current
|
### Current
|
||||||
|
|
||||||
|
|||||||
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 51 KiB |
@@ -4,22 +4,35 @@ use crate::entries::EntryDeclaration;
|
|||||||
use crate::extractors::ExtractorDeclaration;
|
use crate::extractors::ExtractorDeclaration;
|
||||||
use crate::generators::GeneratorDeclaration;
|
use crate::generators::GeneratorDeclaration;
|
||||||
use crate::phases::PhasesConfiguration;
|
use crate::phases::PhasesConfiguration;
|
||||||
use crate::utils;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::Deref;
|
|
||||||
use uefi::proto::device_path::LoadedImageDevicePath;
|
|
||||||
|
|
||||||
|
/// The configuration loader mechanisms.
|
||||||
|
pub mod loader;
|
||||||
|
|
||||||
|
/// This is the latest version of the sprout configuration format.
|
||||||
|
/// This must be incremented when the configuration breaks compatibility.
|
||||||
|
pub const LATEST_VERSION: u32 = 1;
|
||||||
|
|
||||||
|
/// The Sprout configuration format.
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct RootConfiguration {
|
pub struct RootConfiguration {
|
||||||
|
/// The version of the configuration. This should always be declared
|
||||||
|
/// and be the latest version that is supported. If not specified, it is assumed
|
||||||
|
/// the configuration is the latest version.
|
||||||
#[serde(default = "latest_version")]
|
#[serde(default = "latest_version")]
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
|
/// Values to be inserted into the root sprout context.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub values: BTreeMap<String, String>,
|
pub values: BTreeMap<String, String>,
|
||||||
|
/// Drivers to load.
|
||||||
|
/// These drivers provide extra functionality like filesystem support to Sprout.
|
||||||
|
/// Each driver has a name which uniquely identifies it inside Sprout.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub drivers: BTreeMap<String, DriverDeclaration>,
|
pub drivers: BTreeMap<String, DriverDeclaration>,
|
||||||
|
/// Declares the extractors that add values to the sprout context that are calculated
|
||||||
|
/// at runtime. Each extractor has a name which corresponds to the value it will set
|
||||||
|
/// inside the sprout context.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub extractors: BTreeMap<String, ExtractorDeclaration>,
|
pub extractors: BTreeMap<String, ExtractorDeclaration>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -32,19 +45,6 @@ pub struct RootConfiguration {
|
|||||||
pub phases: PhasesConfiguration,
|
pub phases: PhasesConfiguration,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn latest_version() -> u32 {
|
fn latest_version() -> u32 {
|
||||||
1
|
LATEST_VERSION
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load() -> Result<RootConfiguration> {
|
|
||||||
let current_image_device_path_protocol =
|
|
||||||
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(uefi::boot::image_handle())
|
|
||||||
.context("unable to get loaded image device path")?;
|
|
||||||
let path = current_image_device_path_protocol.deref().to_boxed();
|
|
||||||
|
|
||||||
let content = utils::read_file_contents(&path, "sprout.toml")
|
|
||||||
.context("unable to read sprout.toml file")?;
|
|
||||||
let config: RootConfiguration =
|
|
||||||
toml::from_slice(&content).context("unable to parse sprout.toml file")?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/config/loader.rs
Normal file
40
src/config/loader.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use crate::config::{RootConfiguration, latest_version};
|
||||||
|
use crate::utils;
|
||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use toml::Value;
|
||||||
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
||||||
|
|
||||||
|
fn load_raw_config() -> Result<Vec<u8>> {
|
||||||
|
let current_image_device_path_protocol =
|
||||||
|
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(uefi::boot::image_handle())
|
||||||
|
.context("unable to get loaded image device path")?;
|
||||||
|
let path = current_image_device_path_protocol.deref().to_boxed();
|
||||||
|
|
||||||
|
let content = utils::read_file_contents(&path, "sprout.toml")
|
||||||
|
.context("unable to read sprout.toml file")?;
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load() -> Result<RootConfiguration> {
|
||||||
|
let content = load_raw_config()?;
|
||||||
|
let value: Value = toml::from_slice(&content).context("unable to parse sprout.toml file")?;
|
||||||
|
|
||||||
|
let version = value
|
||||||
|
.get("version")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| Value::Integer(latest_version() as i64));
|
||||||
|
|
||||||
|
let version: u32 = version
|
||||||
|
.try_into()
|
||||||
|
.context("unable to get configuration version")?;
|
||||||
|
|
||||||
|
if version != latest_version() {
|
||||||
|
bail!("unsupported configuration version: {}", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config: RootConfiguration = value
|
||||||
|
.try_into()
|
||||||
|
.context("unable to parse sprout.toml file")?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
/// Declares a boot entry to display in the boot menu.
|
||||||
|
///
|
||||||
|
/// Entries are the user-facing concept of Sprout, making it possible
|
||||||
|
/// to run a set of actions with a specific context.
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct EntryDeclaration {
|
pub struct EntryDeclaration {
|
||||||
|
/// The title of the entry which will be display in the boot menu.
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
/// The actions to run when the entry is selected.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub actions: Vec<String>,
|
pub actions: Vec<String>,
|
||||||
|
/// The values to insert into the context when the entry is selected.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub values: BTreeMap<String, String>,
|
pub values: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|||||||
58
src/main.rs
58
src/main.rs
@@ -1,3 +1,4 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
#![feature(uefi_std)]
|
#![feature(uefi_std)]
|
||||||
|
|
||||||
use crate::context::{RootContext, SproutContext};
|
use crate::context::{RootContext, SproutContext};
|
||||||
@@ -20,15 +21,20 @@ pub mod phases;
|
|||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
/// The main entrypoint of sprout.
|
||||||
|
/// It is possible this function will not return if actions that are executed
|
||||||
|
/// exit boot services or do not return control to sprout.
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
// Initialize the basic UEFI environment.
|
||||||
setup::init()?;
|
setup::init()?;
|
||||||
|
|
||||||
let config = config::load()?;
|
// Load the configuration of sprout.
|
||||||
|
// At this point, the configuration has been validated and the specified
|
||||||
if config.version != config::latest_version() {
|
// version is checked to ensure compatibility.
|
||||||
bail!("unsupported configuration version: {}", config.version);
|
let config = config::loader::load()?;
|
||||||
}
|
|
||||||
|
|
||||||
|
// Load the root context.
|
||||||
|
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
||||||
let mut root = {
|
let mut root = {
|
||||||
let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::<
|
let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::<
|
||||||
LoadedImageDevicePath,
|
LoadedImageDevicePath,
|
||||||
@@ -42,16 +48,25 @@ fn main() -> Result<()> {
|
|||||||
RootContext::new(loaded_image_path)
|
RootContext::new(loaded_image_path)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Insert the configuration actions into the root context.
|
||||||
root.actions_mut().extend(config.actions.clone());
|
root.actions_mut().extend(config.actions.clone());
|
||||||
|
|
||||||
|
// Create a new sprout context with the root context.
|
||||||
let mut context = SproutContext::new(root);
|
let mut context = SproutContext::new(root);
|
||||||
|
|
||||||
|
// Insert the configuration values into the sprout context.
|
||||||
context.insert(&config.values);
|
context.insert(&config.values);
|
||||||
|
|
||||||
|
// Freeze the sprout context so it can be shared and cheaply cloned.
|
||||||
let context = context.freeze();
|
let context = context.freeze();
|
||||||
|
|
||||||
|
// Execute the early phase.
|
||||||
phase(context.clone(), &config.phases.early).context("unable to execute early phase")?;
|
phase(context.clone(), &config.phases.early).context("unable to execute early phase")?;
|
||||||
|
|
||||||
|
// Load all configured drivers.
|
||||||
drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?;
|
drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?;
|
||||||
|
|
||||||
|
// Run all the extractors declared in the configuration.
|
||||||
let mut extracted = BTreeMap::new();
|
let mut extracted = BTreeMap::new();
|
||||||
for (name, extractor) in &config.extractors {
|
for (name, extractor) in &config.extractors {
|
||||||
let value = extractors::extract(context.clone(), extractor)
|
let value = extractors::extract(context.clone(), extractor)
|
||||||
@@ -60,50 +75,69 @@ fn main() -> Result<()> {
|
|||||||
extracted.insert(name.clone(), value);
|
extracted.insert(name.clone(), value);
|
||||||
}
|
}
|
||||||
let mut context = context.fork();
|
let mut context = context.fork();
|
||||||
|
// Insert the extracted values into the sprout context.
|
||||||
context.insert(&extracted);
|
context.insert(&extracted);
|
||||||
let context = context.freeze();
|
let context = context.freeze();
|
||||||
|
|
||||||
|
// Execute the late phase.
|
||||||
phase(context.clone(), &config.phases.startup).context("unable to execute startup phase")?;
|
phase(context.clone(), &config.phases.startup).context("unable to execute startup phase")?;
|
||||||
|
|
||||||
let mut all_entries = Vec::new();
|
let mut staged_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 {
|
||||||
all_entries.push((context.clone(), entry));
|
// Associate the main context with the static entry.
|
||||||
|
staged_entries.push((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();
|
let context = context.fork().freeze();
|
||||||
|
|
||||||
|
// 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)? {
|
for entry in generators::generate(context.clone(), &generator)? {
|
||||||
all_entries.push(entry);
|
staged_entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a list of all the final boot entries.
|
||||||
let mut final_entries = Vec::new();
|
let mut final_entries = Vec::new();
|
||||||
for (context, entry) in all_entries {
|
for (context, entry) in staged_entries {
|
||||||
let mut context = context.fork();
|
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.values);
|
||||||
let context = context.finalize().freeze();
|
let context = context.finalize().freeze();
|
||||||
|
|
||||||
|
// Insert the entry configuration into final boot entries with the extended context.
|
||||||
final_entries.push((context, entry));
|
final_entries.push((context, entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(azenla): Implement boot menu here.
|
||||||
|
// For now, we just print all of the entries.
|
||||||
info!("entries:");
|
info!("entries:");
|
||||||
for (index, (context, entry)) in final_entries.iter().enumerate() {
|
for (index, (context, entry)) in final_entries.iter().enumerate() {
|
||||||
let title = context.stamp(&entry.title);
|
let title = context.stamp(&entry.title);
|
||||||
info!(" entry {}: {}", index + 1, title);
|
info!(" entry {}: {}", index + 1, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute the late phase.
|
||||||
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
|
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
|
||||||
|
|
||||||
let index = 1;
|
// Pick the first entry from the list of final entries until a boot menu is implemented.
|
||||||
|
let Some((context, entry)) = final_entries.first() else {
|
||||||
let (context, entry) = &final_entries[index - 1];
|
bail!("no entries found");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute all the actions for the selected entry.
|
||||||
for action in &entry.actions {
|
for action in &entry.actions {
|
||||||
let action = context.stamp(action);
|
let action = context.stamp(action);
|
||||||
actions::execute(context.clone(), &action)
|
actions::execute(context.clone(), &action)
|
||||||
.context(format!("unable to execute action '{}'", action))?;
|
.context(format!("unable to execute action '{}'", action))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sprout doesn't necessarily guarantee anything was booted.
|
||||||
|
// If we reach here, we will exit back to whoever called us.
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,40 @@
|
|||||||
use crate::actions;
|
use crate::actions;
|
||||||
use crate::context::SproutContext;
|
use crate::context::SproutContext;
|
||||||
use anyhow::Context;
|
use anyhow::{Context, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Configures the various phases of the boot process.
|
||||||
|
/// This allows hooking various phases to run actions.
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct PhasesConfiguration {
|
pub struct PhasesConfiguration {
|
||||||
|
/// The early phase is run before drivers are loaded.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub early: Vec<PhaseConfiguration>,
|
pub early: Vec<PhaseConfiguration>,
|
||||||
|
/// The startup phase is run after drivers are loaded, but before entries are displayed.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub startup: Vec<PhaseConfiguration>,
|
pub startup: Vec<PhaseConfiguration>,
|
||||||
|
/// The late phase is run after the entry is chosen, but before the actions are executed.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub late: Vec<PhaseConfiguration>,
|
pub late: Vec<PhaseConfiguration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct PhaseConfiguration {
|
pub struct PhaseConfiguration {
|
||||||
|
/// The actions to run when the phase is executed.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub actions: Vec<String>,
|
pub actions: Vec<String>,
|
||||||
|
/// The values to insert into the context when the phase is executed.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub values: BTreeMap<String, String>,
|
pub values: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn phase(context: Rc<SproutContext>, phase: &[PhaseConfiguration]) -> anyhow::Result<()> {
|
/// Executes the specified [phase] of the boot process.
|
||||||
|
/// The value [phase] should be a reference of a specific phase in the [PhasesConfiguration].
|
||||||
|
/// Any error from the actions is propagated into the [Result] and will interrupt further
|
||||||
|
/// execution of phase actions.
|
||||||
|
pub fn phase(context: Rc<SproutContext>, phase: &[PhaseConfiguration]) -> Result<()> {
|
||||||
for item in phase {
|
for item in phase {
|
||||||
let mut context = context.fork();
|
let mut context = context.fork();
|
||||||
context.insert(&item.values);
|
context.insert(&item.values);
|
||||||
|
|||||||
Reference in New Issue
Block a user