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, HmiConfig};
39use crate::compile::utils::{
40 clean_path, elf_path, inject_csti, inject_hmi, resolve_cargo, resolve_rustc, run_command,
41 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 sysroot: Option<PathBuf>,
200 pub workspace: Option<CstiWorkspace>,
204}
205
206pub struct ApplicationCompiler;
208
209impl ApplicationCompiler {
210 pub async fn build_with_reporter(
211 config: ApplicationConfiguration,
212 reporter: Option<&dyn BuildReporter>,
213 ) -> Result<PathBuf, ApplicationCompileError> {
214 Self::cargo(&config, "build", reporter).await?;
215
216 let elf = Self::output_path(&config).await?;
217
218 if !elf.exists() {
219 return Err(ApplicationCompileError::ElfNotFound(elf));
220 }
221
222 verify_elf(&elf)
223 .await
224 .map_err(ApplicationCompileError::UtilityError)?;
225
226 Ok(elf)
227 }
228
229 pub async fn check_with_reporter(
233 config: &ApplicationConfiguration,
234 reporter: Option<&dyn BuildReporter>,
235 ) -> Result<(), ApplicationCompileError> {
236 Self::cargo(config, "check", reporter).await
237 }
238
239 async fn cargo(
242 config: &ApplicationConfiguration,
243 subcommand: &str,
244 reporter: Option<&dyn BuildReporter>,
245 ) -> Result<(), ApplicationCompileError> {
246 let sdk = SdkEnv::resolve(config.sysroot.as_deref())?;
247
248 let rustc = resolve_rustc()
249 .await
250 .map_err(ApplicationCompileError::RustupError)?;
251 let cargo = resolve_cargo()
252 .await
253 .map_err(ApplicationCompileError::RustupError)?;
254
255 let rustflags = sdk.rustflags();
256 let sanitized_path = clean_path(&sdk.native_sysroot);
257
258 let mut cmd = Command::new(&cargo);
259 cmd.arg(subcommand)
260 .arg("--target")
261 .arg(&config.target)
262 .current_dir(&config.path)
263 .env("PATH", &sanitized_path)
264 .env("RUSTC", &rustc)
265 .env("RUSTFLAGS", &rustflags)
266 .env("SYSROOT", &sdk.sysroot)
267 .env("PKG_CONFIG_SYSROOT_DIR", &sdk.sysroot)
268 .env(
269 "PKG_CONFIG_PATH",
270 format!(
271 "{s}/usr/lib/pkgconfig:{s}/usr/share/pkgconfig",
272 s = sdk.sysroot,
273 ),
274 );
275
276 if let Some(ref cc) = sdk.cross.cc {
277 cmd.arg("--config")
278 .arg(format!("{}.linker={cc}", config.target));
279 cmd.env("CC", format!("{cc} --sysroot={}", sdk.sysroot));
280 }
281
282 if let Some(ref cxx) = sdk.cross.cxx {
283 cmd.env("CXX", format!("{cxx} --sysroot={}", sdk.sysroot));
284 }
285
286 inject_hmi(&mut cmd, config.hmi.as_ref());
287
288 inject_csti(&mut cmd, config.workspace.as_ref());
289
290 if config.release {
291 cmd.arg("--release");
292 }
293
294 if config.offline {
295 cmd.arg("--offline");
296 }
297
298 let status = run_command(&mut cmd, reporter)
299 .await
300 .map_err(|e| ApplicationCompileError::BuildFailed(e.to_string()))?;
301
302 if !status.success() {
303 return Err(ApplicationCompileError::BuildFailed(format!(
304 "cargo {subcommand} failed with status: {status}"
305 )));
306 }
307 Ok(())
308 }
309}
310
311impl Compiler for ApplicationCompiler {
312 type Configuration = ApplicationConfiguration;
313 type Error = ApplicationCompileError;
314 type Output = PathBuf;
315
316 async fn build(config: Self::Configuration) -> Result<Self::Output, Self::Error> {
317 Self::build_with_reporter(config, None).await
318 }
319
320 async fn output_path(config: &Self::Configuration) -> Result<PathBuf, Self::Error> {
321 elf_path(
322 &config.path,
323 config.workspace.as_ref(),
324 &config.target,
325 config.release,
326 )
327 .await
328 .map_err(ApplicationCompileError::UtilityError)
329 }
330}
331
332pub struct ApplicationBuilder {
339 pub path: PathBuf,
341 pub release: bool,
342 pub target: String,
343 pub hmi: Option<HmiConfig>,
344 pub sysroot: Option<PathBuf>,
345}
346
347impl ApplicationBuilder {
348 pub async fn build(&self) -> Result<PathBuf, String> {
350 ApplicationCompiler::build(ApplicationConfiguration {
351 path: self.path.clone(),
352 target: self.target.clone(),
353 release: self.release,
354 offline: false,
355 hmi: self.hmi.clone(),
356 sysroot: self.sysroot.clone(),
357 workspace: None,
358 })
359 .await
360 .map_err(|e| e.to_string())
361 }
362
363 pub async fn elf_path(&self) -> Result<PathBuf, String> {
365 ApplicationCompiler::output_path(&ApplicationConfiguration {
366 path: self.path.clone(),
367 target: self.target.clone(),
368 release: self.release,
369 offline: false,
370 hmi: self.hmi.clone(),
371 sysroot: self.sysroot.clone(),
372 workspace: None,
373 })
374 .await
375 .map_err(|e| e.to_string())
376 }
377}