Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions src/commands/ext/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use crate::utils::tui::{TaskId, TuiGuard};
struct OverlayConfig {
dir: String,
mode: OverlayMode,
/// Opt-in `{{ }}` preprocessing of overlay file contents.
preprocess: crate::utils::overlay_preprocess::PreprocessSpec,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -554,12 +556,14 @@ impl ExtBuildCommand {
})?;

// Get overlay configuration
let overlay_config = ext_config.get("overlay").map(|v| {
let mut overlay_config = ext_config.get("overlay").map(|v| {
use crate::utils::overlay_preprocess::PreprocessSpec;
if let Some(dir_str) = v.as_str() {
// Simple string format: overlay = "directory"
OverlayConfig {
dir: dir_str.to_string(),
mode: OverlayMode::Merge, // Default to merge mode
preprocess: PreprocessSpec::None,
}
} else if let Some(table) = v.as_mapping() {
// Table format: overlay = {dir = "directory", mode = "opaque"}
Expand All @@ -574,12 +578,17 @@ impl ExtBuildCommand {
_ => OverlayMode::Merge, // Default to merge mode
};

OverlayConfig { dir, mode }
OverlayConfig {
dir,
mode,
preprocess: PreprocessSpec::from_overlay_value(v),
}
} else {
// Fallback for invalid format
OverlayConfig {
dir: "overlay".to_string(),
mode: OverlayMode::Merge,
preprocess: PreprocessSpec::None,
}
}
});
Expand Down Expand Up @@ -616,6 +625,58 @@ impl ExtBuildCommand {
ExtensionLocation::Local { .. } => "/opt/src".to_string(),
};

// Opt-in overlay preprocessing (local extensions only). Materialize an
// interpolated copy of the overlay on the host under
// `.avocado/overlay-staging/` and redirect the in-container copy to it,
// so `{{ ... }}` in overlay files is substituted without mutating the
// working tree. Remote-extension overlays live in the SDK volume (not on
// the host), so they can't be preprocessed here — warn and skip.
if let Some(oc) = overlay_config.as_mut() {
if oc.preprocess.is_enabled() {
match &extension_location {
ExtensionLocation::Local { .. } => {
let project_root = config.project_root(&self.config_path);
let context = crate::utils::interpolation::AvocadoContext::from_main_config(
parsed,
Some(target.as_str()),
);
let label = format!(
"ext-{}",
self.extension
.chars()
.map(|c| if c.is_alphanumeric() || c == '-' || c == '_' {
c
} else {
'_'
})
.collect::<String>()
);
if let Some(staging_rel_dir) =
crate::utils::overlay_preprocess::materialize_preprocessed_overlay(
&project_root,
&oc.dir,
&label,
&oc.preprocess,
parsed,
&context,
)?
{
oc.dir = staging_rel_dir;
}
}
ExtensionLocation::Remote { name, .. } => {
print_warning(
&format!(
"Overlay preprocessing is not supported for remote extension \
'{name}'; copying overlay verbatim."
),
OutputLevel::Normal,
);
}
}
}
}

// Run the post_build hook before sealing any .raw images. By this
// point the ext sysroot has been populated by `avocado install` and
// any compile-dep install scripts above, so the hook can freely
Expand Down Expand Up @@ -1913,6 +1974,7 @@ mod tests {
let overlay_config = OverlayConfig {
dir: "peridio".to_string(),
mode: OverlayMode::Merge,
preprocess: crate::utils::overlay_preprocess::PreprocessSpec::None,
};
let script = cmd.create_sysext_build_script(
"1.0",
Expand Down Expand Up @@ -1955,6 +2017,7 @@ mod tests {
let overlay_config = OverlayConfig {
dir: "peridio".to_string(),
mode: OverlayMode::Merge,
preprocess: crate::utils::overlay_preprocess::PreprocessSpec::None,
};
let script = cmd.create_confext_build_script(
"1.0",
Expand Down Expand Up @@ -1997,6 +2060,7 @@ mod tests {
let overlay_config = OverlayConfig {
dir: "peridio".to_string(),
mode: OverlayMode::Opaque,
preprocess: crate::utils::overlay_preprocess::PreprocessSpec::None,
};
let script = cmd.create_sysext_build_script(
"1.0",
Expand Down Expand Up @@ -2040,6 +2104,7 @@ mod tests {
let overlay_config = OverlayConfig {
dir: "peridio".to_string(),
mode: OverlayMode::Opaque,
preprocess: crate::utils::overlay_preprocess::PreprocessSpec::None,
};
let script = cmd.create_confext_build_script(
"1.0",
Expand Down
45 changes: 35 additions & 10 deletions src/commands/rootfs/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,21 +656,46 @@ pub async fn install_sysroot(params: &mut SysrootInstallParams<'_>) -> Result<()

// Build optional overlay snippet — appended to the install command so it
// runs in the same container invocation immediately after DNF finishes.
let overlay_snippet = params
.parsed
.and_then(|parsed| {
let overlay_snippet = {
let overlay_value = params.parsed.and_then(|parsed| {
let key = match params.sysroot_type {
SysrootType::Rootfs => "rootfs",
SysrootType::Initramfs => "initramfs",
_ => return None,
};
parsed.get(key)?.get("overlay")
})
.map(|v| {
let (dir, opaque) = parse_overlay_config(v);
build_overlay_script(&dir, opaque, sysroot_dir)
})
.unwrap_or_default();
parsed.get(key)?.get("overlay").cloned()
});
if let (Some(v), Some(parsed)) = (overlay_value, params.parsed) {
let (dir, opaque) = parse_overlay_config(&v);
// Opt-in preprocessing: materialize an interpolated copy of the
// overlay on the host under `.avocado/overlay-staging/` and copy
// from there, so `{{ ... }}` in overlay files is substituted without
// mutating the working tree.
let spec = crate::utils::overlay_preprocess::PreprocessSpec::from_overlay_value(&v);
let effective_dir = if spec.is_enabled() {
let context = crate::utils::interpolation::AvocadoContext::from_main_config(
parsed,
Some(params.target),
);
match crate::utils::overlay_preprocess::materialize_preprocessed_overlay(
params.src_dir,
&dir,
sysroot_dir,
&spec,
parsed,
&context,
)? {
Some(staging_rel_dir) => staging_rel_dir,
None => dir,
}
} else {
dir
};
build_overlay_script(&effective_dir, opaque, sysroot_dir)
} else {
String::new()
}
};

let exclude_str = if off_kernel_excludes.is_empty() {
String::new()
Expand Down
23 changes: 23 additions & 0 deletions src/utils/interpolation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,29 @@ pub fn interpolate_config_with_context(
Ok(())
}

/// Preprocess `{{ ... }}` templates inside arbitrary file contents (e.g. an
/// overlay file), using the same `env.`/`config.`/`avocado.` resolution as the
/// YAML interpolator.
///
/// `root` is the composed config tree (for `{{ config.* }}`); `context` carries
/// the resolved target/board/distro values (for `{{ avocado.* }}`). Missing
/// `env.*` variables warn and resolve to an empty string, identical to
/// `avocado.yaml` interpolation. Input that contains no templates is returned
/// unchanged.
pub fn preprocess_text(input: &str, root: &Value, context: &AvocadoContext) -> Result<String> {
let mut resolving_stack = HashSet::new();
let path: Vec<String> = Vec::new();
Ok(interpolate_string(
input,
root,
context,
&mut resolving_stack,
&path,
&YamlLocation::Value,
)?
.unwrap_or_else(|| input.to_string()))
}

/// Represents where in the YAML structure a template was found.
#[derive(Clone, Debug)]
enum YamlLocation {
Expand Down
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod lockfile;
pub mod nfs_server;
pub mod output;
pub mod output_format;
pub mod overlay_preprocess;
pub mod permissions;
pub mod pkcs11_devices;
pub mod prerequisites;
Expand Down
Loading
Loading