consortium_builder/compile/
application.rs1use std::path::{Path, PathBuf};
18use thiserror::Error;
19use tokio::process::Command;
20
21use super::Compiler;
22use crate::compile::env::{DbgConfig, HmiConfig, SharedMemConfig};
23use crate::compile::utils::{
24 clean_path, elf_path, inject_dbg, inject_hmi, inject_shared_mem, resolve_cargo, resolve_rustc,
25 run_command, verify_elf,
26};
27use crate::progress::BuildReporter;
28
29#[derive(Error, Debug)]
31pub enum ApplicationCompileError {
32 #[error("Failed to resolve sysroot: {0}")]
33 SysrootError(String),
34
35 #[error("Failed to resolve rustup tool: {0}")]
36 RustupError(String),
37
38 #[error("Cargo build failed: {0}")]
39 BuildFailed(String),
40
41 #[error("ELF not found at expected path: {0:?}")]
42 ElfNotFound(PathBuf),
43
44 #[error("{0}")]
45 UtilityError(String),
46}
47
48struct CrossCompiler {
50 cc: Option<String>,
52 cxx: Option<String>,
54}
55
56impl CrossCompiler {
57 fn detect() -> Self {
61 if let Ok(cc) = std::env::var("CC") {
63 let cxx = std::env::var("CXX").unwrap_or_else(|_| cc.clone());
64 return CrossCompiler {
65 cc: Some(cc),
66 cxx: Some(cxx),
67 };
68 }
69
70 CrossCompiler {
72 cc: None,
73 cxx: None,
74 }
75 }
76}
77
78struct SdkEnv {
79 sysroot: String,
80 cross: CrossCompiler,
81 arch_flags: String,
83 native_sysroot: String,
85}
86
87impl SdkEnv {
88 fn resolve(sysroot_override: Option<&Path>) -> Result<Self, ApplicationCompileError> {
95 let sysroot = match sysroot_override {
96 Some(path) => path.to_string_lossy().into_owned(),
97 None => std::env::var("SDKTARGETSYSROOT")
98 .or_else(|_| std::env::var("OECORE_TARGET_SYSROOT"))
99 .map_err(|_| {
100 ApplicationCompileError::SysrootError(
101 "Could not determine target sysroot. Set the [profile] sysroot in \
102 Consortium.toml, export SDKTARGETSYSROOT, or source a Yocto \
103 environment-setup script."
104 .to_string(),
105 )
106 })?,
107 };
108
109 if !PathBuf::from(&sysroot).exists() {
110 return Err(ApplicationCompileError::SysrootError(format!(
111 "Target sysroot does not exist: {sysroot}"
112 )));
113 }
114
115 let native_sysroot = std::env::var("OECORE_NATIVE_SYSROOT").unwrap_or_default();
117
118 let arch_flags = std::env::var("OECORE_TUNE_CCARGS")
119 .unwrap_or_else(|_| "-mcpu=cortex-a35+crc -mbranch-protection=standard".to_string());
120
121 Ok(SdkEnv {
122 sysroot,
123 cross: CrossCompiler::detect(),
124 arch_flags,
125 native_sysroot,
126 })
127 }
128
129 fn rustflags(&self) -> String {
130 let arch_link_args: String = self
131 .arch_flags
132 .split_whitespace()
133 .map(|f| format!(" -C link-arg={f}"))
134 .collect();
135
136 match &self.cross.cc {
137 Some(cc) => format!(
138 "-C linker={cc} -C link-arg=--sysroot={sysroot}{arch_link_args}",
139 sysroot = self.sysroot,
140 ),
141 None => format!(
142 "-C link-arg=--sysroot={sysroot}{arch_link_args}",
143 sysroot = self.sysroot,
144 ),
145 }
146 }
147}
148
149#[derive(Clone)]
167pub struct ApplicationConfiguration {
168 pub path: PathBuf,
170 pub target: String,
172 pub release: bool,
174 pub hmi: Option<HmiConfig>,
177 pub ipc: Option<SharedMemConfig>,
179 pub dbg: Option<DbgConfig>,
181 pub sysroot: Option<PathBuf>,
185}
186
187pub struct ApplicationCompiler;
189
190impl ApplicationCompiler {
191 pub async fn build_with_reporter(
192 config: ApplicationConfiguration,
193 reporter: Option<&dyn BuildReporter>,
194 ) -> Result<PathBuf, ApplicationCompileError> {
195 let sdk = SdkEnv::resolve(config.sysroot.as_deref())?;
196
197 let rustc = resolve_rustc()
198 .await
199 .map_err(ApplicationCompileError::RustupError)?;
200 let cargo = resolve_cargo()
201 .await
202 .map_err(ApplicationCompileError::RustupError)?;
203
204 let rustflags = sdk.rustflags();
205 let sanitized_path = clean_path(&sdk.native_sysroot);
206
207 let mut cmd = Command::new(&cargo);
208 cmd.arg("build")
209 .arg("--target")
210 .arg(&config.target)
211 .current_dir(&config.path)
212 .env("PATH", &sanitized_path)
213 .env("RUSTC", &rustc)
214 .env("RUSTFLAGS", &rustflags)
215 .env("SYSROOT", &sdk.sysroot)
216 .env("PKG_CONFIG_SYSROOT_DIR", &sdk.sysroot)
217 .env(
218 "PKG_CONFIG_PATH",
219 format!(
220 "{s}/usr/lib/pkgconfig:{s}/usr/share/pkgconfig",
221 s = sdk.sysroot,
222 ),
223 );
224
225 if let Some(ref cc) = sdk.cross.cc {
226 cmd.arg("--config")
227 .arg(format!("{}.linker={cc}", config.target));
228 cmd.env("CC", format!("{cc} --sysroot={}", sdk.sysroot));
229 }
230
231 if let Some(ref cxx) = sdk.cross.cxx {
232 cmd.env("CXX", format!("{cxx} --sysroot={}", sdk.sysroot));
233 }
234
235 inject_hmi(&mut cmd, config.hmi.as_ref());
236
237 inject_shared_mem(&mut cmd, "IPC", config.ipc.as_ref());
238 inject_dbg(&mut cmd, config.dbg.as_ref());
239
240 if config.release {
241 cmd.arg("--release");
242 }
243
244 let status = run_command(&mut cmd, reporter)
245 .await
246 .map_err(|e| ApplicationCompileError::BuildFailed(e.to_string()))?;
247
248 if !status.success() {
249 return Err(ApplicationCompileError::BuildFailed(format!(
250 "cargo build failed with status: {status}"
251 )));
252 }
253
254 let elf = Self::output_path(&config).await?;
255
256 if !elf.exists() {
257 return Err(ApplicationCompileError::ElfNotFound(elf));
258 }
259
260 verify_elf(&elf)
261 .await
262 .map_err(ApplicationCompileError::UtilityError)?;
263
264 Ok(elf)
265 }
266}
267
268impl Compiler for ApplicationCompiler {
269 type Configuration = ApplicationConfiguration;
270 type Error = ApplicationCompileError;
271 type Output = PathBuf;
272
273 async fn build(config: Self::Configuration) -> Result<Self::Output, Self::Error> {
274 Self::build_with_reporter(config, None).await
275 }
276
277 async fn output_path(config: &Self::Configuration) -> Result<PathBuf, Self::Error> {
278 elf_path(&config.path, &config.target, config.release)
279 .await
280 .map_err(ApplicationCompileError::UtilityError)
281 }
282}
283
284pub struct ApplicationBuilder {
289 pub path: PathBuf,
291 pub release: bool,
292 pub target: String,
293 pub hmi: Option<HmiConfig>,
294 pub ipc: Option<SharedMemConfig>,
295 pub dbg: Option<DbgConfig>,
296 pub sysroot: Option<PathBuf>,
297}
298
299impl ApplicationBuilder {
300 pub async fn build(&self) -> Result<PathBuf, String> {
302 ApplicationCompiler::build(ApplicationConfiguration {
303 path: self.path.clone(),
304 target: self.target.clone(),
305 release: self.release,
306 hmi: self.hmi.clone(),
307 ipc: self.ipc,
308 dbg: self.dbg,
309 sysroot: self.sysroot.clone(),
310 })
311 .await
312 .map_err(|e| e.to_string())
313 }
314
315 pub async fn elf_path(&self) -> Result<PathBuf, String> {
317 ApplicationCompiler::output_path(&ApplicationConfiguration {
318 path: self.path.clone(),
319 target: self.target.clone(),
320 release: self.release,
321 hmi: self.hmi.clone(),
322 ipc: self.ipc,
323 dbg: self.dbg,
324 sysroot: self.sysroot.clone(),
325 })
326 .await
327 .map_err(|e| e.to_string())
328 }
329}