use std::collections::HashMap;
use std::path::{Path, PathBuf};
use consortium_cfg::Session;
use consortium_cfg_common::config::{AcoreName, Party, VisibleCore};
use super::{CargoOptions, PipelineError, elf_map};
use crate::compile::application::ApplicationConfiguration;
use crate::compile::env::{CstiWorkspace, HmiConfig};
pub struct ApplicationBuildPlan {
pub config: ApplicationConfiguration,
pub generated: String,
}
pub fn plan_application_build(
session: &Session,
workspace: &CstiWorkspace,
hmi_dist: Option<&Path>,
resolved_dbg_elfs: &HashMap<VisibleCore, PathBuf>,
cargo: CargoOptions,
ty_crate: &syn::Path,
) -> Result<ApplicationBuildPlan, PipelineError> {
let root = session
.config
.endpoints
.get(&Party::AcoreNonSecure)
.ok_or_else(|| PipelineError::MissingEndpoint(Party::AcoreNonSecure.to_string()))?;
let manifest_dir = &workspace.manifest_dir;
let path = manifest_dir.join(root);
let target = acore_linux_triple(&session.database.ap)?;
let hmi = match (&session.config.hmi, hmi_dist) {
(Some(hmi), Some(_)) => Some(HmiConfig::from_hmi(
hmi,
&session.config.profile.deploy_root,
)),
_ => None,
};
let sysroot = session
.config
.profile
.sysroot
.as_ref()
.map(|s| manifest_dir.join(s));
let mut ir = session.lower()?;
let embedded = elf_map::embed_locations(resolved_dbg_elfs, workspace);
ir.dbg = std::mem::take(&mut ir.dbg).with_resolved_elf_paths(&embedded);
consortium_log::trace!("rendering AP init module for target {}", target);
let generated =
consortium_cfg::render(ir.initialize(VisibleCore::Ap, ty_crate)).map_err(|e| {
consortium_log::trace!("AP init codegen render failed: {}", e);
PipelineError::Codegen(e.to_string())
})?;
Ok(ApplicationBuildPlan {
config: ApplicationConfiguration {
path,
target,
release: cargo.release,
offline: cargo.offline,
hmi,
sysroot,
workspace: Some(workspace.clone()),
},
generated,
})
}
pub(crate) fn acore_linux_triple(ap: &str) -> Result<String, PipelineError> {
let name: AcoreName = ap
.parse()
.map_err(|_| PipelineError::UnknownCore(ap.to_string()))?;
Ok(if name.is_64_bit() {
"aarch64-unknown-linux-gnu".to_string()
} else {
"armv7-unknown-linux-gnueabihf".to_string()
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::resolve_workspace;
const STM32: &str = include_str!("../../../consortium-cfg/Consortium.example.stm32mp257.toml");
fn cm33() -> VisibleCore {
VisibleCore::Mcore("cm33".parse().unwrap())
}
#[test]
fn acore_triple_tracks_bitness() {
assert_eq!(
acore_linux_triple("ca35").unwrap(),
"aarch64-unknown-linux-gnu"
);
assert_eq!(
acore_linux_triple("ca7").unwrap(),
"armv7-unknown-linux-gnueabihf"
);
assert!(acore_linux_triple("bogus").is_err());
}
#[test]
fn plans_application_with_hmi_and_embedded_dbg() {
let session = Session::from_toml(STM32).expect("manifest loads");
let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
let resolved = HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]);
let plan = plan_application_build(
&session,
&resolve_workspace(&session, Path::new("/proj")).unwrap(),
Some(Path::new("/build/website/dist")),
&resolved,
CargoOptions {
release: true,
offline: true,
},
&ty_crate,
)
.expect("plan succeeds");
assert_eq!(plan.config.path, Path::new("/proj/app"));
assert_eq!(plan.config.target, "aarch64-unknown-linux-gnu");
assert!(plan.config.release);
assert!(plan.config.offline);
let hmi = plan.config.hmi.expect("hmi injected");
assert_eq!(
hmi,
HmiConfig::Cog {
dist: "/opt/consortium/www".to_string(),
debug_url: Some("http://127.0.0.1:5173/".to_string()),
}
);
assert!(
plan.generated
.contains("include_bytes!(\"/build/cm33.elf\")")
);
assert!(!plan.generated.contains("ConsortiumLogger"));
assert!(plan.config.sysroot.is_none());
}
#[test]
fn embeds_debug_elf_relative_to_mcu_path() {
let session = Session::from_toml(STM32).expect("manifest loads");
let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
let workspace = resolve_workspace(&session, Path::new("/proj")).unwrap();
let elf = "/proj/firmware/target/thumbv8m.main-none-eabihf/debug/mcu_original.elf";
let plan = plan_application_build(
&session,
&workspace,
None,
&HashMap::from([(cm33(), PathBuf::from(elf))]),
CargoOptions::default(),
&ty_crate,
)
.expect("plan succeeds");
assert!(!plan.generated.contains("/proj"), "{}", plan.generated);
assert!(
plan.generated.contains("::core::env!(\"CSTI_MCU_PATH\")"),
"{}",
plan.generated
);
assert!(
plan.generated
.contains("\"/target/thumbv8m.main-none-eabihf/debug/mcu_original.elf\"")
);
assert_eq!(plan.config.workspace, Some(workspace));
}
#[test]
fn relative_profile_sysroot_anchors_at_manifest_dir() {
let toml = STM32.replace(
"deploy_root = \"/opt/consortium\"",
"deploy_root = \"/opt/consortium\"\nsysroot = \"sdk/sysroot\"",
);
let session = Session::from_toml(&toml).expect("manifest loads");
let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
let plan = plan_application_build(
&session,
&resolve_workspace(&session, Path::new("/proj")).unwrap(),
None,
&HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
CargoOptions::default(),
&ty_crate,
)
.expect("plan succeeds");
assert_eq!(
plan.config.sysroot.as_deref(),
Some(Path::new("/proj/sdk/sysroot"))
);
}
#[test]
fn absolute_profile_sysroot_used_verbatim() {
let toml = STM32.replace(
"deploy_root = \"/opt/consortium\"",
"deploy_root = \"/opt/consortium\"\nsysroot = \"/sdk/target/sysroot\"",
);
let session = Session::from_toml(&toml).expect("manifest loads");
let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
let plan = plan_application_build(
&session,
&resolve_workspace(&session, Path::new("/proj")).unwrap(),
None,
&HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
CargoOptions::default(),
&ty_crate,
)
.expect("plan succeeds");
assert_eq!(
plan.config.sysroot.as_deref(),
Some(Path::new("/sdk/target/sysroot"))
);
}
#[test]
fn omits_hmi_when_frontend_not_built() {
let session = Session::from_toml(STM32).expect("manifest loads");
let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
let plan = plan_application_build(
&session,
&resolve_workspace(&session, Path::new("/proj")).unwrap(),
None,
&HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
CargoOptions::default(),
&ty_crate,
)
.expect("plan succeeds");
assert!(plan.config.hmi.is_none());
}
}