consortium_builder/pipeline/
application.rs1use std::collections::HashMap;
27use std::path::{Path, PathBuf};
28
29use consortium_cfg::Session;
30use consortium_cfg_common::config::{AcoreName, Party, VisibleCore};
31
32use super::{CargoOptions, PipelineError, elf_map, ipc_config_for};
33use crate::compile::application::ApplicationConfiguration;
34use crate::compile::env::{CstiWorkspace, HmiConfig};
35
36pub struct ApplicationBuildPlan {
38 pub config: ApplicationConfiguration,
40 pub generated: String,
44}
45
46pub fn plan_application_build(
53 session: &Session,
54 workspace: &CstiWorkspace,
55 hmi_dist: Option<&Path>,
56 resolved_dbg_elfs: &HashMap<VisibleCore, PathBuf>,
57 cargo: CargoOptions,
58 ty_crate: &syn::Path,
59) -> Result<ApplicationBuildPlan, PipelineError> {
60 let root = session
61 .config
62 .endpoints
63 .get(&Party::AcoreNonSecure)
64 .ok_or_else(|| PipelineError::MissingEndpoint(Party::AcoreNonSecure.to_string()))?;
65 let manifest_dir = &workspace.manifest_dir;
66 let path = manifest_dir.join(root);
67
68 let target = acore_linux_triple(&session.database.ap)?;
69
70 let hmi = match (&session.config.hmi, hmi_dist) {
74 (Some(hmi), Some(_)) => Some(HmiConfig::from_hmi(
75 hmi,
76 &session.config.profile.deploy_root,
77 )),
78 _ => None,
79 };
80
81 let ipc = ipc_config_for(&session.lower()?, VisibleCore::Ap);
82
83 let sysroot = session
86 .config
87 .profile
88 .sysroot
89 .as_ref()
90 .map(|s| manifest_dir.join(s));
91
92 let mut ir = session.lower()?;
95 let embedded = elf_map::embed_locations(resolved_dbg_elfs, workspace);
96 ir.dbg = std::mem::take(&mut ir.dbg).with_resolved_elf_paths(&embedded);
97 consortium_log::trace!("rendering AP init module for target {}", target);
98 let generated =
99 consortium_cfg::render(ir.initialize(VisibleCore::Ap, ty_crate)).map_err(|e| {
100 consortium_log::trace!("AP init codegen render failed: {}", e);
101 PipelineError::Codegen(e.to_string())
102 })?;
103
104 Ok(ApplicationBuildPlan {
105 config: ApplicationConfiguration {
106 path,
107 target,
108 release: cargo.release,
109 offline: cargo.offline,
110 hmi,
111 ipc,
112 dbg: None,
116 sysroot,
117 workspace: Some(workspace.clone()),
118 },
119 generated,
120 })
121}
122
123pub(crate) fn acore_linux_triple(ap: &str) -> Result<String, PipelineError> {
129 let name: AcoreName = ap
130 .parse()
131 .map_err(|_| PipelineError::UnknownCore(ap.to_string()))?;
132 Ok(if name.is_64_bit() {
133 "aarch64-unknown-linux-gnu".to_string()
134 } else {
135 "armv7-unknown-linux-gnueabihf".to_string()
136 })
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::pipeline::resolve_workspace;
143
144 const STM32: &str = include_str!("../../../consortium-cfg/Consortium.example.stm32mp257.toml");
145
146 fn cm33() -> VisibleCore {
147 VisibleCore::Mcore("cm33".parse().unwrap())
148 }
149
150 #[test]
151 fn acore_triple_tracks_bitness() {
152 assert_eq!(
153 acore_linux_triple("ca35").unwrap(),
154 "aarch64-unknown-linux-gnu"
155 );
156 assert_eq!(
157 acore_linux_triple("ca7").unwrap(),
158 "armv7-unknown-linux-gnueabihf"
159 );
160 assert!(acore_linux_triple("bogus").is_err());
161 }
162
163 #[test]
164 fn plans_application_with_hmi_and_embedded_dbg() {
165 let session = Session::from_toml(STM32).expect("manifest loads");
166 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
167 let resolved = HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]);
168
169 let plan = plan_application_build(
170 &session,
171 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
172 Some(Path::new("/build/website/dist")),
173 &resolved,
174 CargoOptions {
175 release: true,
176 offline: true,
177 },
178 &ty_crate,
179 )
180 .expect("plan succeeds");
181
182 assert_eq!(plan.config.path, Path::new("/proj/app"));
183 assert_eq!(plan.config.target, "aarch64-unknown-linux-gnu");
184 assert!(plan.config.release);
185 assert!(plan.config.offline);
187
188 let hmi = plan.config.hmi.expect("hmi injected");
190 assert_eq!(
191 hmi,
192 HmiConfig::Cog {
193 dist: "/opt/consortium/www".to_string(),
194 debug_url: Some("http://127.0.0.1:5173/".to_string()),
195 }
196 );
197
198 assert!(
200 plan.generated
201 .contains("include_bytes!(\"/build/cm33.elf\")")
202 );
203 assert!(plan.config.dbg.is_none());
205
206 assert!(plan.config.sysroot.is_none());
208 }
209
210 #[test]
211 fn embeds_debug_elf_relative_to_mcu_path() {
212 let session = Session::from_toml(STM32).expect("manifest loads");
216 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
217 let workspace = resolve_workspace(&session, Path::new("/proj")).unwrap();
218 let elf = "/proj/firmware/target/thumbv8m.main-none-eabihf/debug/mcu_original.elf";
219
220 let plan = plan_application_build(
221 &session,
222 &workspace,
223 None,
224 &HashMap::from([(cm33(), PathBuf::from(elf))]),
225 CargoOptions::default(),
226 &ty_crate,
227 )
228 .expect("plan succeeds");
229
230 assert!(!plan.generated.contains("/proj"), "{}", plan.generated);
231 assert!(
232 plan.generated.contains("::core::env!(\"CSTI_MCU_PATH\")"),
233 "{}",
234 plan.generated
235 );
236 assert!(
237 plan.generated
238 .contains("\"/target/thumbv8m.main-none-eabihf/debug/mcu_original.elf\"")
239 );
240 assert_eq!(plan.config.workspace, Some(workspace));
241 }
242
243 #[test]
244 fn relative_profile_sysroot_anchors_at_manifest_dir() {
245 let toml = STM32.replace(
246 "deploy_root = \"/opt/consortium\"",
247 "deploy_root = \"/opt/consortium\"\nsysroot = \"sdk/sysroot\"",
248 );
249 let session = Session::from_toml(&toml).expect("manifest loads");
250 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
251
252 let plan = plan_application_build(
253 &session,
254 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
255 None,
256 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
257 CargoOptions::default(),
258 &ty_crate,
259 )
260 .expect("plan succeeds");
261
262 assert_eq!(
263 plan.config.sysroot.as_deref(),
264 Some(Path::new("/proj/sdk/sysroot"))
265 );
266 }
267
268 #[test]
269 fn absolute_profile_sysroot_used_verbatim() {
270 let toml = STM32.replace(
271 "deploy_root = \"/opt/consortium\"",
272 "deploy_root = \"/opt/consortium\"\nsysroot = \"/sdk/target/sysroot\"",
273 );
274 let session = Session::from_toml(&toml).expect("manifest loads");
275 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
276
277 let plan = plan_application_build(
278 &session,
279 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
280 None,
281 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
282 CargoOptions::default(),
283 &ty_crate,
284 )
285 .expect("plan succeeds");
286
287 assert_eq!(
288 plan.config.sysroot.as_deref(),
289 Some(Path::new("/sdk/target/sysroot"))
290 );
291 }
292
293 #[test]
294 fn omits_hmi_when_frontend_not_built() {
295 let session = Session::from_toml(STM32).expect("manifest loads");
296 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
297 let plan = plan_application_build(
298 &session,
299 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
300 None,
301 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
302 CargoOptions::default(),
303 &ty_crate,
304 )
305 .expect("plan succeeds");
306 assert!(plan.config.hmi.is_none());
307 }
308}