consortium_builder/pipeline/
application.rs1use std::collections::HashMap;
24use std::path::{Path, PathBuf};
25
26use consortium_cfg::Session;
27use consortium_cfg_common::config::{AcoreName, Party, VisibleCore};
28
29use super::{PipelineError, ipc_config_for};
30use crate::compile::application::ApplicationConfiguration;
31use crate::compile::env::HmiConfig;
32
33pub struct ApplicationBuildPlan {
35 pub config: ApplicationConfiguration,
37 pub generated: String,
41}
42
43pub fn plan_application_build(
50 session: &Session,
51 manifest_dir: &Path,
52 hmi_dist: Option<&Path>,
53 resolved_dbg_elfs: &HashMap<VisibleCore, PathBuf>,
54 release: bool,
55 ty_crate: &syn::Path,
56) -> Result<ApplicationBuildPlan, PipelineError> {
57 let root = session
58 .config
59 .endpoints
60 .get(&Party::AcoreNonSecure)
61 .ok_or_else(|| PipelineError::MissingEndpoint(Party::AcoreNonSecure.to_string()))?;
62 let path = manifest_dir.join(root);
63
64 let target = acore_linux_triple(&session.database.ap)?;
65
66 let hmi = match (&session.config.hmi, hmi_dist) {
70 (Some(hmi), Some(_)) => Some(HmiConfig::from_hmi(
71 hmi,
72 &session.config.profile.deploy_root,
73 )),
74 _ => None,
75 };
76
77 let ipc = ipc_config_for(&session.lower()?, VisibleCore::Ap);
78
79 let sysroot = session
82 .config
83 .profile
84 .sysroot
85 .as_ref()
86 .map(|s| manifest_dir.join(s));
87
88 let mut ir = session.lower()?;
91 ir.dbg = std::mem::take(&mut ir.dbg).with_resolved_elf_paths(resolved_dbg_elfs);
92 consortium_log::trace!("rendering AP init module for target {}", target);
93 let generated =
94 consortium_cfg::render(ir.initialize(VisibleCore::Ap, ty_crate)).map_err(|e| {
95 consortium_log::trace!("AP init codegen render failed: {}", e);
96 PipelineError::Codegen(e.to_string())
97 })?;
98
99 Ok(ApplicationBuildPlan {
100 config: ApplicationConfiguration {
101 path,
102 target,
103 release,
104 hmi,
105 ipc,
106 dbg: None,
110 sysroot,
111 },
112 generated,
113 })
114}
115
116fn 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
136 const STM32: &str = include_str!("../../../consortium-cfg/Consortium.example.stm32mp257.toml");
137
138 fn cm33() -> VisibleCore {
139 VisibleCore::Mcore("cm33".parse().unwrap())
140 }
141
142 #[test]
143 fn acore_triple_tracks_bitness() {
144 assert_eq!(
145 acore_linux_triple("ca35").unwrap(),
146 "aarch64-unknown-linux-gnu"
147 );
148 assert_eq!(
149 acore_linux_triple("ca7").unwrap(),
150 "armv7-unknown-linux-gnueabihf"
151 );
152 assert!(acore_linux_triple("bogus").is_err());
153 }
154
155 #[test]
156 fn plans_application_with_hmi_and_embedded_dbg() {
157 let session = Session::from_toml(STM32).expect("manifest loads");
158 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
159 let resolved = HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]);
160
161 let plan = plan_application_build(
162 &session,
163 Path::new("/proj"),
164 Some(Path::new("/build/website/dist")),
165 &resolved,
166 true,
167 &ty_crate,
168 )
169 .expect("plan succeeds");
170
171 assert_eq!(plan.config.path, Path::new("/proj/app"));
172 assert_eq!(plan.config.target, "aarch64-unknown-linux-gnu");
173 assert!(plan.config.release);
174
175 let hmi = plan.config.hmi.expect("hmi injected");
177 assert_eq!(
178 hmi,
179 HmiConfig::Cog {
180 dist: "/opt/consortium/www".to_string(),
181 debug_url: Some("http://127.0.0.1:5173/".to_string()),
182 }
183 );
184
185 assert!(
187 plan.generated
188 .contains("include_bytes!(\"/build/cm33.elf\")")
189 );
190 assert!(plan.config.dbg.is_none());
192
193 assert!(plan.config.sysroot.is_none());
195 }
196
197 #[test]
198 fn relative_profile_sysroot_anchors_at_manifest_dir() {
199 let toml = STM32.replace(
200 "deploy_root = \"/opt/consortium\"",
201 "deploy_root = \"/opt/consortium\"\nsysroot = \"sdk/sysroot\"",
202 );
203 let session = Session::from_toml(&toml).expect("manifest loads");
204 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
205
206 let plan = plan_application_build(
207 &session,
208 Path::new("/proj"),
209 None,
210 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
211 false,
212 &ty_crate,
213 )
214 .expect("plan succeeds");
215
216 assert_eq!(
217 plan.config.sysroot.as_deref(),
218 Some(Path::new("/proj/sdk/sysroot"))
219 );
220 }
221
222 #[test]
223 fn absolute_profile_sysroot_used_verbatim() {
224 let toml = STM32.replace(
225 "deploy_root = \"/opt/consortium\"",
226 "deploy_root = \"/opt/consortium\"\nsysroot = \"/sdk/target/sysroot\"",
227 );
228 let session = Session::from_toml(&toml).expect("manifest loads");
229 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
230
231 let plan = plan_application_build(
232 &session,
233 Path::new("/proj"),
234 None,
235 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
236 false,
237 &ty_crate,
238 )
239 .expect("plan succeeds");
240
241 assert_eq!(
242 plan.config.sysroot.as_deref(),
243 Some(Path::new("/sdk/target/sysroot"))
244 );
245 }
246
247 #[test]
248 fn omits_hmi_when_frontend_not_built() {
249 let session = Session::from_toml(STM32).expect("manifest loads");
250 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
251 let plan = plan_application_build(
252 &session,
253 Path::new("/proj"),
254 None,
255 &HashMap::from([(cm33(), PathBuf::from("/build/cm33.elf"))]),
256 false,
257 &ty_crate,
258 )
259 .expect("plan succeeds");
260 assert!(plan.config.hmi.is_none());
261 }
262}