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};
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 sysroot = session
84 .config
85 .profile
86 .sysroot
87 .as_ref()
88 .map(|s| manifest_dir.join(s));
89
90 let mut ir = session.lower()?;
93 let embedded = elf_map::embed_locations(resolved_dbg_elfs, workspace);
94 ir.dbg = std::mem::take(&mut ir.dbg).with_resolved_elf_paths(&embedded);
95 consortium_log::trace!("rendering AP init module for target {}", target);
96 let generated =
97 consortium_cfg::render(ir.initialize(VisibleCore::Ap, ty_crate)).map_err(|e| {
98 consortium_log::trace!("AP init codegen render failed: {}", e);
99 PipelineError::Codegen(e.to_string())
100 })?;
101
102 Ok(ApplicationBuildPlan {
103 config: ApplicationConfiguration {
104 path,
105 target,
106 release: cargo.release,
107 offline: cargo.offline,
108 hmi,
109 sysroot,
110 workspace: Some(workspace.clone()),
111 },
112 generated,
113 })
114}
115
116pub(crate) fn acore_linux_triple(ap: &str) -> Result<String, PipelineError> {
122 let name: AcoreName = ap
123 .parse()
124 .map_err(|_| PipelineError::UnknownCore(ap.to_string()))?;
125 Ok(if name.is_64_bit() {
126 "aarch64-unknown-linux-gnu".to_string()
127 } else {
128 "armv7-unknown-linux-gnueabihf".to_string()
129 })
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::pipeline::resolve_workspace;
136
137 const STM32: &str = include_str!("../../../consortium-cfg/Consortium.example.stm32mp257.toml");
138
139 fn cm33() -> VisibleCore {
140 VisibleCore::Mcore("cm33".parse().unwrap())
141 }
142
143 #[test]
144 fn acore_triple_tracks_bitness() {
145 assert_eq!(
146 acore_linux_triple("ca35").unwrap(),
147 "aarch64-unknown-linux-gnu"
148 );
149 assert_eq!(
150 acore_linux_triple("ca7").unwrap(),
151 "armv7-unknown-linux-gnueabihf"
152 );
153 assert!(acore_linux_triple("bogus").is_err());
154 }
155
156 #[test]
157 fn plans_application_with_hmi_and_embedded_dbg() {
158 let session = Session::from_toml(STM32).expect("manifest loads");
159 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
160 let resolved = HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]);
161
162 let plan = plan_application_build(
163 &session,
164 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
165 Some(Path::new("/build/website/dist")),
166 &resolved,
167 CargoOptions {
168 release: true,
169 offline: true,
170 },
171 &ty_crate,
172 )
173 .expect("plan succeeds");
174
175 assert_eq!(plan.config.path, Path::new("/proj/app"));
176 assert_eq!(plan.config.target, "aarch64-unknown-linux-gnu");
177 assert!(plan.config.release);
178 assert!(plan.config.offline);
180
181 let hmi = plan.config.hmi.expect("hmi injected");
183 assert_eq!(
184 hmi,
185 HmiConfig::Cog {
186 dist: "/opt/consortium/www".to_string(),
187 debug_url: Some("http://127.0.0.1:5173/".to_string()),
188 }
189 );
190
191 assert!(
193 plan.generated
194 .contains("include_bytes!(\"/build/cm33.elf\")")
195 );
196 assert!(!plan.generated.contains("ConsortiumLogger"));
198
199 assert!(plan.config.sysroot.is_none());
201 }
202
203 #[test]
204 fn embeds_debug_elf_relative_to_mcu_path() {
205 let session = Session::from_toml(STM32).expect("manifest loads");
209 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
210 let workspace = resolve_workspace(&session, Path::new("/proj")).unwrap();
211 let elf = "/proj/firmware/target/thumbv8m.main-none-eabihf/debug/mcu_original.elf";
212
213 let plan = plan_application_build(
214 &session,
215 &workspace,
216 None,
217 &HashMap::from([(cm33(), PathBuf::from(elf))]),
218 CargoOptions::default(),
219 &ty_crate,
220 )
221 .expect("plan succeeds");
222
223 assert!(!plan.generated.contains("/proj"), "{}", plan.generated);
224 assert!(
225 plan.generated.contains("::core::env!(\"CSTI_MCU_PATH\")"),
226 "{}",
227 plan.generated
228 );
229 assert!(
230 plan.generated
231 .contains("\"/target/thumbv8m.main-none-eabihf/debug/mcu_original.elf\"")
232 );
233 assert_eq!(plan.config.workspace, Some(workspace));
234 }
235
236 #[test]
237 fn relative_profile_sysroot_anchors_at_manifest_dir() {
238 let toml = STM32.replace(
239 "deploy_root = \"/opt/consortium\"",
240 "deploy_root = \"/opt/consortium\"\nsysroot = \"sdk/sysroot\"",
241 );
242 let session = Session::from_toml(&toml).expect("manifest loads");
243 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
244
245 let plan = plan_application_build(
246 &session,
247 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
248 None,
249 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
250 CargoOptions::default(),
251 &ty_crate,
252 )
253 .expect("plan succeeds");
254
255 assert_eq!(
256 plan.config.sysroot.as_deref(),
257 Some(Path::new("/proj/sdk/sysroot"))
258 );
259 }
260
261 #[test]
262 fn absolute_profile_sysroot_used_verbatim() {
263 let toml = STM32.replace(
264 "deploy_root = \"/opt/consortium\"",
265 "deploy_root = \"/opt/consortium\"\nsysroot = \"/sdk/target/sysroot\"",
266 );
267 let session = Session::from_toml(&toml).expect("manifest loads");
268 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
269
270 let plan = plan_application_build(
271 &session,
272 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
273 None,
274 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
275 CargoOptions::default(),
276 &ty_crate,
277 )
278 .expect("plan succeeds");
279
280 assert_eq!(
281 plan.config.sysroot.as_deref(),
282 Some(Path::new("/sdk/target/sysroot"))
283 );
284 }
285
286 #[test]
287 fn omits_hmi_when_frontend_not_built() {
288 let session = Session::from_toml(STM32).expect("manifest loads");
289 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
290 let plan = plan_application_build(
291 &session,
292 &resolve_workspace(&session, Path::new("/proj")).unwrap(),
293 None,
294 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
295 CargoOptions::default(),
296 &ty_crate,
297 )
298 .expect("plan succeeds");
299 assert!(plan.config.hmi.is_none());
300 }
301}