consortium_builder/compile/
application.rs1use std::path::{Path, PathBuf};
34use thiserror::Error;
35use tokio::process::Command;
36
37use super::Compiler;
38use crate::compile::env::{DbgConfig, HmiConfig, SharedMemConfig};
39use crate::compile::utils::{
40 clean_path, elf_path, inject_dbg, inject_hmi, inject_shared_mem, resolve_cargo, resolve_rustc,
41 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 build 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 hmi: Option<HmiConfig>,
193 pub ipc: Option<SharedMemConfig>,
195 pub dbg: Option<DbgConfig>,
197 pub sysroot: Option<PathBuf>,
201}
202
203pub struct ApplicationCompiler;
205
206impl ApplicationCompiler {
207 pub async fn build_with_reporter(
208 config: ApplicationConfiguration,
209 reporter: Option<&dyn BuildReporter>,
210 ) -> Result<PathBuf, ApplicationCompileError> {
211 let sdk = SdkEnv::resolve(config.sysroot.as_deref())?;
212
213 let rustc = resolve_rustc()
214 .await
215 .map_err(ApplicationCompileError::RustupError)?;
216 let cargo = resolve_cargo()
217 .await
218 .map_err(ApplicationCompileError::RustupError)?;
219
220 let rustflags = sdk.rustflags();
221 let sanitized_path = clean_path(&sdk.native_sysroot);
222
223 let mut cmd = Command::new(&cargo);
224 cmd.arg("build")
225 .arg("--target")
226 .arg(&config.target)
227 .current_dir(&config.path)
228 .env("PATH", &sanitized_path)
229 .env("RUSTC", &rustc)
230 .env("RUSTFLAGS", &rustflags)
231 .env("SYSROOT", &sdk.sysroot)
232 .env("PKG_CONFIG_SYSROOT_DIR", &sdk.sysroot)
233 .env(
234 "PKG_CONFIG_PATH",
235 format!(
236 "{s}/usr/lib/pkgconfig:{s}/usr/share/pkgconfig",
237 s = sdk.sysroot,
238 ),
239 );
240
241 if let Some(ref cc) = sdk.cross.cc {
242 cmd.arg("--config")
243 .arg(format!("{}.linker={cc}", config.target));
244 cmd.env("CC", format!("{cc} --sysroot={}", sdk.sysroot));
245 }
246
247 if let Some(ref cxx) = sdk.cross.cxx {
248 cmd.env("CXX", format!("{cxx} --sysroot={}", sdk.sysroot));
249 }
250
251 inject_hmi(&mut cmd, config.hmi.as_ref());
252
253 inject_shared_mem(&mut cmd, "IPC", config.ipc.as_ref());
254 inject_dbg(&mut cmd, config.dbg.as_ref());
255
256 if config.release {
257 cmd.arg("--release");
258 }
259
260 let status = run_command(&mut cmd, reporter)
261 .await
262 .map_err(|e| ApplicationCompileError::BuildFailed(e.to_string()))?;
263
264 if !status.success() {
265 return Err(ApplicationCompileError::BuildFailed(format!(
266 "cargo build failed with status: {status}"
267 )));
268 }
269
270 let elf = Self::output_path(&config).await?;
271
272 if !elf.exists() {
273 return Err(ApplicationCompileError::ElfNotFound(elf));
274 }
275
276 verify_elf(&elf)
277 .await
278 .map_err(ApplicationCompileError::UtilityError)?;
279
280 Ok(elf)
281 }
282}
283
284impl Compiler for ApplicationCompiler {
285 type Configuration = ApplicationConfiguration;
286 type Error = ApplicationCompileError;
287 type Output = PathBuf;
288
289 async fn build(config: Self::Configuration) -> Result<Self::Output, Self::Error> {
290 Self::build_with_reporter(config, None).await
291 }
292
293 async fn output_path(config: &Self::Configuration) -> Result<PathBuf, Self::Error> {
294 elf_path(&config.path, &config.target, config.release)
295 .await
296 .map_err(ApplicationCompileError::UtilityError)
297 }
298}
299
300pub struct ApplicationBuilder {
305 pub path: PathBuf,
307 pub release: bool,
308 pub target: String,
309 pub hmi: Option<HmiConfig>,
310 pub ipc: Option<SharedMemConfig>,
311 pub dbg: Option<DbgConfig>,
312 pub sysroot: Option<PathBuf>,
313}
314
315impl ApplicationBuilder {
316 pub async fn build(&self) -> Result<PathBuf, String> {
318 ApplicationCompiler::build(ApplicationConfiguration {
319 path: self.path.clone(),
320 target: self.target.clone(),
321 release: self.release,
322 hmi: self.hmi.clone(),
323 ipc: self.ipc,
324 dbg: self.dbg,
325 sysroot: self.sysroot.clone(),
326 })
327 .await
328 .map_err(|e| e.to_string())
329 }
330
331 pub async fn elf_path(&self) -> Result<PathBuf, String> {
333 ApplicationCompiler::output_path(&ApplicationConfiguration {
334 path: self.path.clone(),
335 target: self.target.clone(),
336 release: self.release,
337 hmi: self.hmi.clone(),
338 ipc: self.ipc,
339 dbg: self.dbg,
340 sysroot: self.sysroot.clone(),
341 })
342 .await
343 .map_err(|e| e.to_string())
344 }
345}