consortium_builder/compile/
application.rs1use std::path::{Path, PathBuf};
34use thiserror::Error;
35use tokio::process::Command;
36
37use super::Compiler;
38use crate::compile::env::{CstiWorkspace, DbgConfig, HmiConfig, SharedMemConfig};
39use crate::compile::utils::{
40 clean_path, elf_path, inject_csti, inject_dbg, inject_hmi, inject_shared_mem, resolve_cargo,
41 resolve_rustc, run_command, verify_elf,
42};
43use crate::progress::BuildReporter;
44
45#[derive(Error, Debug)]
47pub enum ApplicationCompileError {
48 #[error("Failed to resolve sysroot: {0}")]
49 SysrootError(String),
50
51 #[error("Failed to resolve rustup tool: {0}")]
52 RustupError(String),
53
54 #[error("Cargo failed: {0}")]
55 BuildFailed(String),
56
57 #[error("ELF not found at expected path: {0:?}")]
58 ElfNotFound(PathBuf),
59
60 #[error("{0}")]
61 UtilityError(String),
62}
63
64struct CrossCompiler {
66 cc: Option<String>,
68 cxx: Option<String>,
70}
71
72impl CrossCompiler {
73 fn detect() -> Self {
77 if let Ok(cc) = std::env::var("CC") {
79 let cxx = std::env::var("CXX").unwrap_or_else(|_| cc.clone());
80 return CrossCompiler {
81 cc: Some(cc),
82 cxx: Some(cxx),
83 };
84 }
85
86 CrossCompiler {
88 cc: None,
89 cxx: None,
90 }
91 }
92}
93
94struct SdkEnv {
95 sysroot: String,
96 cross: CrossCompiler,
97 arch_flags: String,
99 native_sysroot: String,
101}
102
103impl SdkEnv {
104 fn resolve(sysroot_override: Option<&Path>) -> Result<Self, ApplicationCompileError> {
111 let sysroot = match sysroot_override {
112 Some(path) => path.to_string_lossy().into_owned(),
113 None => std::env::var("SDKTARGETSYSROOT")
114 .or_else(|_| std::env::var("OECORE_TARGET_SYSROOT"))
115 .map_err(|_| {
116 ApplicationCompileError::SysrootError(
117 "Could not determine target sysroot. Set the [profile] sysroot in \
118 Consortium.toml, export SDKTARGETSYSROOT, or source a Yocto \
119 environment-setup script."
120 .to_string(),
121 )
122 })?,
123 };
124
125 if !PathBuf::from(&sysroot).exists() {
126 return Err(ApplicationCompileError::SysrootError(format!(
127 "Target sysroot does not exist: {sysroot}"
128 )));
129 }
130
131 let native_sysroot = std::env::var("OECORE_NATIVE_SYSROOT").unwrap_or_default();
133
134 let arch_flags = std::env::var("OECORE_TUNE_CCARGS")
135 .unwrap_or_else(|_| "-mcpu=cortex-a35+crc -mbranch-protection=standard".to_string());
136
137 Ok(SdkEnv {
138 sysroot,
139 cross: CrossCompiler::detect(),
140 arch_flags,
141 native_sysroot,
142 })
143 }
144
145 fn rustflags(&self) -> String {
146 let arch_link_args: String = self
147 .arch_flags
148 .split_whitespace()
149 .map(|f| format!(" -C link-arg={f}"))
150 .collect();
151
152 match &self.cross.cc {
153 Some(cc) => format!(
154 "-C linker={cc} -C link-arg=--sysroot={sysroot}{arch_link_args}",
155 sysroot = self.sysroot,
156 ),
157 None => format!(
158 "-C link-arg=--sysroot={sysroot}{arch_link_args}",
159 sysroot = self.sysroot,
160 ),
161 }
162 }
163}
164
165#[derive(Clone)]
183pub struct ApplicationConfiguration {
184 pub path: PathBuf,
186 pub target: String,
188 pub release: bool,
190 pub offline: bool,
193 pub hmi: Option<HmiConfig>,
196 pub ipc: Option<SharedMemConfig>,
198 pub dbg: Option<DbgConfig>,
200 pub sysroot: Option<PathBuf>,
204 pub workspace: Option<CstiWorkspace>,
208}
209
210pub struct ApplicationCompiler;
212
213impl ApplicationCompiler {
214 pub async fn build_with_reporter(
215 config: ApplicationConfiguration,
216 reporter: Option<&dyn BuildReporter>,
217 ) -> Result<PathBuf, ApplicationCompileError> {
218 Self::cargo(&config, "build", reporter).await?;
219
220 let elf = Self::output_path(&config).await?;
221
222 if !elf.exists() {
223 return Err(ApplicationCompileError::ElfNotFound(elf));
224 }
225
226 verify_elf(&elf)
227 .await
228 .map_err(ApplicationCompileError::UtilityError)?;
229
230 Ok(elf)
231 }
232
233 pub async fn check_with_reporter(
237 config: &ApplicationConfiguration,
238 reporter: Option<&dyn BuildReporter>,
239 ) -> Result<(), ApplicationCompileError> {
240 Self::cargo(config, "check", reporter).await
241 }
242
243 async fn cargo(
246 config: &ApplicationConfiguration,
247 subcommand: &str,
248 reporter: Option<&dyn BuildReporter>,
249 ) -> Result<(), ApplicationCompileError> {
250 let sdk = SdkEnv::resolve(config.sysroot.as_deref())?;
251
252 let rustc = resolve_rustc()
253 .await
254 .map_err(ApplicationCompileError::RustupError)?;
255 let cargo = resolve_cargo()
256 .await
257 .map_err(ApplicationCompileError::RustupError)?;
258
259 let rustflags = sdk.rustflags();
260 let sanitized_path = clean_path(&sdk.native_sysroot);
261
262 let mut cmd = Command::new(&cargo);
263 cmd.arg(subcommand)
264 .arg("--target")
265 .arg(&config.target)
266 .current_dir(&config.path)
267 .env("PATH", &sanitized_path)
268 .env("RUSTC", &rustc)
269 .env("RUSTFLAGS", &rustflags)
270 .env("SYSROOT", &sdk.sysroot)
271 .env("PKG_CONFIG_SYSROOT_DIR", &sdk.sysroot)
272 .env(
273 "PKG_CONFIG_PATH",
274 format!(
275 "{s}/usr/lib/pkgconfig:{s}/usr/share/pkgconfig",
276 s = sdk.sysroot,
277 ),
278 );
279
280 if let Some(ref cc) = sdk.cross.cc {
281 cmd.arg("--config")
282 .arg(format!("{}.linker={cc}", config.target));
283 cmd.env("CC", format!("{cc} --sysroot={}", sdk.sysroot));
284 }
285
286 if let Some(ref cxx) = sdk.cross.cxx {
287 cmd.env("CXX", format!("{cxx} --sysroot={}", sdk.sysroot));
288 }
289
290 inject_hmi(&mut cmd, config.hmi.as_ref());
291
292 inject_shared_mem(&mut cmd, "IPC", config.ipc.as_ref());
293 inject_dbg(&mut cmd, config.dbg.as_ref());
294 inject_csti(&mut cmd, config.workspace.as_ref());
295
296 if config.release {
297 cmd.arg("--release");
298 }
299
300 if config.offline {
301 cmd.arg("--offline");
302 }
303
304 let status = run_command(&mut cmd, reporter)
305 .await
306 .map_err(|e| ApplicationCompileError::BuildFailed(e.to_string()))?;
307
308 if !status.success() {
309 return Err(ApplicationCompileError::BuildFailed(format!(
310 "cargo {subcommand} failed with status: {status}"
311 )));
312 }
313 Ok(())
314 }
315}
316
317impl Compiler for ApplicationCompiler {
318 type Configuration = ApplicationConfiguration;
319 type Error = ApplicationCompileError;
320 type Output = PathBuf;
321
322 async fn build(config: Self::Configuration) -> Result<Self::Output, Self::Error> {
323 Self::build_with_reporter(config, None).await
324 }
325
326 async fn output_path(config: &Self::Configuration) -> Result<PathBuf, Self::Error> {
327 elf_path(
328 &config.path,
329 config.workspace.as_ref(),
330 &config.target,
331 config.release,
332 )
333 .await
334 .map_err(ApplicationCompileError::UtilityError)
335 }
336}
337
338pub struct ApplicationBuilder {
345 pub path: PathBuf,
347 pub release: bool,
348 pub target: String,
349 pub hmi: Option<HmiConfig>,
350 pub ipc: Option<SharedMemConfig>,
351 pub dbg: Option<DbgConfig>,
352 pub sysroot: Option<PathBuf>,
353}
354
355impl ApplicationBuilder {
356 pub async fn build(&self) -> Result<PathBuf, String> {
358 ApplicationCompiler::build(ApplicationConfiguration {
359 path: self.path.clone(),
360 target: self.target.clone(),
361 release: self.release,
362 offline: false,
363 hmi: self.hmi.clone(),
364 ipc: self.ipc,
365 dbg: self.dbg,
366 sysroot: self.sysroot.clone(),
367 workspace: None,
368 })
369 .await
370 .map_err(|e| e.to_string())
371 }
372
373 pub async fn elf_path(&self) -> Result<PathBuf, String> {
375 ApplicationCompiler::output_path(&ApplicationConfiguration {
376 path: self.path.clone(),
377 target: self.target.clone(),
378 release: self.release,
379 offline: false,
380 hmi: self.hmi.clone(),
381 ipc: self.ipc,
382 dbg: self.dbg,
383 sysroot: self.sysroot.clone(),
384 workspace: None,
385 })
386 .await
387 .map_err(|e| e.to_string())
388 }
389}