From f2b2d9b72d2d7fc619642ded8fb44135f9911554 Mon Sep 17 00:00:00 2001 From: zackees Date: Sat, 30 May 2026 12:03:59 -0700 Subject: [PATCH] feat(build-info): emit build_info_.json after successful link (fixes #297) Adds a post-link emitter that writes a PlatformIO-compatible build_info_.json (and the no-example-fallback build_info.json) to the project directory after a successful sequential build. Outer dict is keyed by env name (matching `pio project metadata --json-output`); inner dict carries prog_path plus toolchain binaries (cc/cxx/ar/objcopy /size) and the compile/link flags fbuild already knows. This unblocks every FastLED size-check and symbol-analysis workflow that was silently failing because fbuild compiles succeeded but downstream _find_build_info() calls couldn't locate the metadata file. Same shape as `pio project metadata --json-output` so FastLED's ci/compiled_size.py::_create_board_info accepts it unmodified. Hooked into pipeline/sequential.rs only -- covers AVR, Teensy, RP2040, STM32, NRF52, SAM, Renesas, Apollo3, CH32V, ESP8266. ESP32 (different pipeline) and WASM (no firmware.elf) intentionally deferred to a follow-up. Linker trait gains three optional accessors (ar_tool_path, objcopy_tool_path, link_driver_path) defaulting to None -- per-platform linkers override to expose the toolchain binaries they already hold. Closes #297. --- crates/fbuild-build/src/avr/avr_linker.rs | 12 + crates/fbuild-build/src/build_info.rs | 305 ++++++++++++++++++ crates/fbuild-build/src/ch32v/ch32v_linker.rs | 12 + crates/fbuild-build/src/esp32/esp32_linker.rs | 12 + .../src/esp8266/esp8266_linker.rs | 12 + .../src/generic_arm/arm_linker.rs | 12 + crates/fbuild-build/src/lib.rs | 1 + crates/fbuild-build/src/linker.rs | 26 ++ crates/fbuild-build/src/nrf52/nrf52_linker.rs | 12 + .../fbuild-build/src/pipeline/sequential.rs | 36 +++ .../src/renesas/renesas_linker.rs | 12 + crates/fbuild-build/src/sam/sam_linker.rs | 12 + .../fbuild-build/src/silabs/silabs_linker.rs | 12 + .../fbuild-build/src/teensy/teensy_linker.rs | 12 + 14 files changed, 488 insertions(+) create mode 100644 crates/fbuild-build/src/build_info.rs diff --git a/crates/fbuild-build/src/avr/avr_linker.rs b/crates/fbuild-build/src/avr/avr_linker.rs index 3d37bbe8..26acc413 100644 --- a/crates/fbuild-build/src/avr/avr_linker.rs +++ b/crates/fbuild-build/src/avr/avr_linker.rs @@ -134,6 +134,18 @@ impl Linker for AvrLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/build_info.rs b/crates/fbuild-build/src/build_info.rs new file mode 100644 index 00000000..6ac974c5 --- /dev/null +++ b/crates/fbuild-build/src/build_info.rs @@ -0,0 +1,305 @@ +//! Post-link emitter for `build_info_.json`. +//! +//! Writes a PlatformIO-compatible `build_info_.json` (and a duplicate +//! `build_info.json` fallback) to the project directory after a successful +//! link. The schema matches `pio project metadata --json-output`: the outer +//! object is keyed by environment name, and the inner object carries the +//! toolchain binaries and effective flags fbuild already knows about. +//! +//! FastLED's `ci/compiled_size.py::_create_board_info`, `ci/inspect_binary.py`, +//! `ci/symbol_analysis_runner.py`, and similar size/symbol tooling consume +//! this file unmodified. Without it, every fbuild-driven size check silently +//! fails because the consumer's `_find_build_info()` lookup can't locate the +//! metadata file PlatformIO would have written. +//! +//! See FastLED/fbuild#297. + +use std::path::{Path, PathBuf}; + +use fbuild_core::Result; +use serde::{Deserialize, Serialize}; + +/// PlatformIO-shape build metadata for one environment. +/// +/// All paths are emitted as strings (matching `pio project metadata` +/// output); empty / missing toolchain entries are emitted as empty strings +/// so consumers that do `Path(board_info["objcopy_path"])` never KeyError. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct BuildInfo { + /// Absolute path to the final firmware/program file (`.elf` if no + /// `.hex`/`.bin` was produced, otherwise the converted firmware). + pub prog_path: String, + /// Absolute path to the C compiler (`gcc`). + pub cc_path: String, + /// Absolute path to the C++ compiler (`g++`). + pub cxx_path: String, + /// Absolute path to `ar` (empty when the linker doesn't expose it). + pub ar_path: String, + /// Absolute path to `objcopy` (empty when the platform has no objcopy step, + /// e.g. ESP8266 which produces ELF directly). + pub objcopy_path: String, + /// Absolute path to `size`. + pub size_path: String, + /// Effective C compile flags as seen by the compiler driver. + pub cc_flags: Vec, + /// Effective C++ compile flags as seen by the compiler driver. + pub cxx_flags: Vec, + /// Effective link flags (does not include object files or `-l` libs). + pub link_flags: Vec, + /// `-D` defines extracted from `cxx_flags`. + pub defines: Vec, + /// `-I` includes extracted from `cxx_flags`. + pub includes: Vec, + /// Libraries passed to the linker (e.g. `-lc`, `-lm`). + pub libs: Vec, + /// Platform identifier (e.g. `atmelavr`, `ststm32`). + pub platform: String, + /// Board identifier (e.g. `uno`, `teensy41`). + pub board: String, + /// PlatformIO env name (e.g. `uno`, `teensy41-debug`). + pub env: String, +} + +impl BuildInfo { + /// Construct a `BuildInfo` from already-collected pieces. Splits + /// `-D` defines and `-I` includes out of `cxx_flags` (matching + /// PlatformIO's metadata-emitter convention) without removing them + /// from `cxx_flags` itself. + #[allow(clippy::too_many_arguments)] + pub fn new( + prog_path: &Path, + cc_path: Option<&Path>, + cxx_path: Option<&Path>, + ar_path: Option<&Path>, + objcopy_path: Option<&Path>, + size_path: &Path, + cc_flags: Vec, + cxx_flags: Vec, + link_flags: Vec, + libs: Vec, + platform: String, + board: String, + env: String, + ) -> Self { + let defines = extract_prefixed(&cxx_flags, "-D"); + let includes = extract_prefixed(&cxx_flags, "-I"); + Self { + prog_path: path_to_string(Some(prog_path)), + cc_path: path_to_string(cc_path), + cxx_path: path_to_string(cxx_path), + ar_path: path_to_string(ar_path), + objcopy_path: path_to_string(objcopy_path), + size_path: path_to_string(Some(size_path)), + cc_flags, + cxx_flags, + link_flags, + defines, + includes, + libs, + platform, + board, + env, + } + } +} + +fn path_to_string(p: Option<&Path>) -> String { + p.map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default() +} + +fn extract_prefixed(flags: &[String], prefix: &str) -> Vec { + flags + .iter() + .filter_map(|f| f.strip_prefix(prefix).map(|s| s.to_string())) + .filter(|s| !s.is_empty()) + .collect() +} + +/// Emit `build_info_.json` and `build_info.json` next to the project's +/// `platformio.ini`. +/// +/// Both files carry the same payload — `build_info.json` is the +/// no-example-name fallback FastLED's `_find_build_info()` walks. Writing +/// failures degrade to `tracing::warn!` rather than failing the build; an +/// otherwise-successful link should never be reported as failed because the +/// downstream metadata file couldn't be written. +pub fn emit_build_info(project_dir: &Path, env_name: &str, info: &BuildInfo) -> Result<()> { + let outer = std::collections::BTreeMap::from([(env_name.to_string(), info.clone())]); + let json = serde_json::to_string_pretty(&outer).map_err(|e| { + fbuild_core::FbuildError::BuildFailed(format!("failed to serialize build_info: {e}")) + })?; + + let env_specific = project_dir.join(format!("build_info_{env_name}.json")); + let generic = project_dir.join("build_info.json"); + + for path in [&env_specific, &generic] { + if let Err(e) = std::fs::write(path, &json) { + tracing::warn!("failed to write {}: {}", path.display(), e); + } + } + Ok(()) +} + +/// Build a `prog_path` candidate by preferring the firmware file (`.hex`/`.bin`) +/// when present and falling back to the ELF. Matches FastLED's expectation +/// that `prog_path.parent / firmware.{bin,uf2,hex}` resolves to actual flash. +pub fn pick_prog_path( + elf: Option<&Path>, + hex: Option<&Path>, + bin: Option<&Path>, +) -> Option { + bin.map(Path::to_path_buf) + .or_else(|| hex.map(Path::to_path_buf)) + .or_else(|| elf.map(Path::to_path_buf)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn sample_info() -> BuildInfo { + BuildInfo::new( + Path::new("/build/firmware.elf"), + Some(Path::new("/bin/avr-gcc")), + Some(Path::new("/bin/avr-g++")), + Some(Path::new("/bin/avr-ar")), + Some(Path::new("/bin/avr-objcopy")), + Path::new("/bin/avr-size"), + vec!["-Os".to_string(), "-DUSER=1".to_string()], + vec![ + "-Os".to_string(), + "-I/inc".to_string(), + "-DFOO=bar".to_string(), + "-DUSER=1".to_string(), + ], + vec!["-Wl,--gc-sections".to_string()], + vec!["-lm".to_string(), "-lc".to_string()], + "atmelavr".to_string(), + "uno".to_string(), + "uno".to_string(), + ) + } + + #[test] + fn build_info_splits_defines_and_includes() { + let info = sample_info(); + assert_eq!( + info.defines, + vec!["FOO=bar".to_string(), "USER=1".to_string()] + ); + assert_eq!(info.includes, vec!["/inc".to_string()]); + // cxx_flags must still carry the originals (defines/includes are a + // *projection* — PlatformIO's metadata-emitter emits both). + assert!(info.cxx_flags.iter().any(|f| f == "-DFOO=bar")); + assert!(info.cxx_flags.iter().any(|f| f == "-I/inc")); + } + + #[test] + fn build_info_handles_missing_optional_tools() { + let info = BuildInfo::new( + Path::new("/build/firmware.elf"), + Some(Path::new("/bin/gcc")), + Some(Path::new("/bin/g++")), + None, // no ar + None, // no objcopy + Path::new("/bin/size"), + vec![], + vec![], + vec![], + vec![], + "esp8266".to_string(), + "nodemcuv2".to_string(), + "nodemcuv2".to_string(), + ); + assert_eq!(info.ar_path, ""); + assert_eq!(info.objcopy_path, ""); + } + + #[test] + fn emit_writes_both_files() { + let tmp = tempfile::TempDir::new().unwrap(); + let info = sample_info(); + emit_build_info(tmp.path(), "uno", &info).unwrap(); + assert!(tmp.path().join("build_info_uno.json").exists()); + assert!(tmp.path().join("build_info.json").exists()); + } + + #[test] + fn emit_outer_dict_has_single_env_key() { + // FastLED's _create_board_info asserts exactly one outer key + // and pulls the inner value via next(iter(...)). + let tmp = tempfile::TempDir::new().unwrap(); + let info = sample_info(); + emit_build_info(tmp.path(), "uno", &info).unwrap(); + + let bytes = std::fs::read(tmp.path().join("build_info_uno.json")).unwrap(); + let parsed: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + let obj = parsed.as_object().expect("outer is object"); + assert_eq!(obj.len(), 1); + assert!(obj.contains_key("uno")); + + let inner = obj + .get("uno") + .unwrap() + .as_object() + .expect("inner is object"); + // Every key FastLED's _create_board_info / check_firmware_size reaches for. + for required in [ + "prog_path", + "cc_path", + "cxx_path", + "ar_path", + "objcopy_path", + "size_path", + ] { + assert!(inner.contains_key(required), "missing key: {required}"); + } + } + + #[test] + fn emit_round_trips_to_struct() { + let tmp = tempfile::TempDir::new().unwrap(); + let info = sample_info(); + emit_build_info(tmp.path(), "uno", &info).unwrap(); + + let bytes = std::fs::read(tmp.path().join("build_info_uno.json")).unwrap(); + let parsed: std::collections::BTreeMap = + serde_json::from_slice(&bytes).unwrap(); + let inner = parsed.get("uno").unwrap(); + assert_eq!(inner, &info); + } + + #[test] + fn pick_prog_path_prefers_bin_then_hex_then_elf() { + let elf = PathBuf::from("/b/firmware.elf"); + let hex = PathBuf::from("/b/firmware.hex"); + let bin = PathBuf::from("/b/firmware.bin"); + + assert_eq!( + pick_prog_path(Some(&elf), Some(&hex), Some(&bin)), + Some(bin.clone()) + ); + assert_eq!( + pick_prog_path(Some(&elf), Some(&hex), None), + Some(hex.clone()) + ); + assert_eq!(pick_prog_path(Some(&elf), None, None), Some(elf.clone())); + assert_eq!(pick_prog_path(None, None, None), None); + } + + #[test] + fn extract_prefixed_handles_empty_and_missing() { + assert_eq!(extract_prefixed(&[], "-D"), Vec::::new()); + assert_eq!( + extract_prefixed(&["-Os".to_string()], "-D"), + Vec::::new() + ); + // Bare "-D" (no value) is filtered out — empty defines aren't useful. + assert_eq!( + extract_prefixed(&["-D".to_string(), "-DX".to_string()], "-D"), + vec!["X".to_string()] + ); + } +} diff --git a/crates/fbuild-build/src/ch32v/ch32v_linker.rs b/crates/fbuild-build/src/ch32v/ch32v_linker.rs index 55850031..331e2292 100644 --- a/crates/fbuild-build/src/ch32v/ch32v_linker.rs +++ b/crates/fbuild-build/src/ch32v/ch32v_linker.rs @@ -132,6 +132,18 @@ impl Linker for Ch32vLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/esp32/esp32_linker.rs b/crates/fbuild-build/src/esp32/esp32_linker.rs index 27839a37..75dafb71 100644 --- a/crates/fbuild-build/src/esp32/esp32_linker.rs +++ b/crates/fbuild-build/src/esp32/esp32_linker.rs @@ -379,6 +379,18 @@ impl Linker for Esp32Linker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { if let Some(size_info) = self.load_cached_size(elf_path) { tracing::info!("size: firmware.elf is unchanged, reusing cached size report"); diff --git a/crates/fbuild-build/src/esp8266/esp8266_linker.rs b/crates/fbuild-build/src/esp8266/esp8266_linker.rs index ce665b47..ce34f28f 100644 --- a/crates/fbuild-build/src/esp8266/esp8266_linker.rs +++ b/crates/fbuild-build/src/esp8266/esp8266_linker.rs @@ -292,6 +292,18 @@ impl Linker for Esp8266Linker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/generic_arm/arm_linker.rs b/crates/fbuild-build/src/generic_arm/arm_linker.rs index a409e861..72f1d834 100644 --- a/crates/fbuild-build/src/generic_arm/arm_linker.rs +++ b/crates/fbuild-build/src/generic_arm/arm_linker.rs @@ -154,6 +154,18 @@ impl Linker for ArmLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/lib.rs b/crates/fbuild-build/src/lib.rs index 32dd28e1..2d9beaca 100644 --- a/crates/fbuild-build/src/lib.rs +++ b/crates/fbuild-build/src/lib.rs @@ -7,6 +7,7 @@ pub mod apollo3; mod arduino_props; pub mod avr; pub mod build_fingerprint; +pub mod build_info; pub mod build_output; pub mod ch32v; pub mod compile_database; diff --git a/crates/fbuild-build/src/linker.rs b/crates/fbuild-build/src/linker.rs index 8ae47df4..a2a45124 100644 --- a/crates/fbuild-build/src/linker.rs +++ b/crates/fbuild-build/src/linker.rs @@ -72,6 +72,32 @@ pub trait Linker: Send + Sync { /// Used to derive the `nm` tool path for symbol analysis. fn size_tool_path(&self) -> &Path; + /// Path to the platform's `ar` archiver, when the linker exposes one. + /// + /// Default: `None`. Per-platform linkers override to expose the binary + /// they store internally. Consumed by [`crate::build_info::emit_build_info`] + /// so downstream tools (FastLED `ci/compiled_size.py`, etc.) can locate + /// the toolchain that produced firmware.elf. See FastLED/fbuild#297. + fn ar_tool_path(&self) -> Option<&Path> { + None + } + + /// Path to the platform's `objcopy`, when the link toolchain has one. + /// + /// Default: `None`. Platforms that produce an ELF directly (e.g. ESP8266 + /// for the .elf output, before esptool processing) legitimately return + /// `None`. See [`Linker::ar_tool_path`]. + fn objcopy_tool_path(&self) -> Option<&Path> { + None + } + + /// Path to the link driver — typically the `gcc`/`g++` binary the linker + /// invokes (not `ld` directly). Used as the `cc_path`/`cxx_path` fallback + /// when downstream tools need a compiler. Default: `None`. + fn link_driver_path(&self) -> Option<&Path> { + None + } + /// Full link pipeline: archive core → link → convert → size → optional symbol analysis. /// /// Skips relinking when the existing firmware.elf is newer than all input diff --git a/crates/fbuild-build/src/nrf52/nrf52_linker.rs b/crates/fbuild-build/src/nrf52/nrf52_linker.rs index b0d40372..e0d81e40 100644 --- a/crates/fbuild-build/src/nrf52/nrf52_linker.rs +++ b/crates/fbuild-build/src/nrf52/nrf52_linker.rs @@ -147,6 +147,18 @@ impl Linker for Nrf52Linker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/pipeline/sequential.rs b/crates/fbuild-build/src/pipeline/sequential.rs index 0894f272..4b66d54e 100644 --- a/crates/fbuild-build/src/pipeline/sequential.rs +++ b/crates/fbuild-build/src/pipeline/sequential.rs @@ -247,6 +247,42 @@ pub fn run_sequential_build_with_libs( )? }; + // Emit build_info_.json (and the generic fallback) so downstream + // tools (FastLED `ci/compiled_size.py`, etc.) can find the toolchain + // and firmware paths after a successful link. Failures degrade to + // tracing::warn! inside `emit_build_info` — never fail the build over + // a metadata-file write. See FastLED/fbuild#297. + { + let _g = perf.phase("build-info"); + let prog_path = crate::build_info::pick_prog_path( + link_result.elf_path.as_deref(), + link_result.hex_path.as_deref(), + link_result.bin_path.as_deref(), + ); + if let Some(prog) = prog_path { + let info = crate::build_info::BuildInfo::new( + &prog, + linker.link_driver_path(), + linker.link_driver_path(), + linker.ar_tool_path(), + linker.objcopy_tool_path(), + linker.size_tool_path(), + compiler.c_flags(), + compiler.cpp_flags(), + ctx.overlay_link_flags.clone(), + ctx.overlay_link_libs.clone(), + platform_label.to_string(), + ctx.board.board.clone(), + params.env_name.clone(), + ); + if let Err(e) = + crate::build_info::emit_build_info(¶ms.project_dir, ¶ms.env_name, &info) + { + tracing::warn!("emit_build_info failed: {e}"); + } + } + } + // Result handle_link_result( &link_result, diff --git a/crates/fbuild-build/src/renesas/renesas_linker.rs b/crates/fbuild-build/src/renesas/renesas_linker.rs index 0206124a..e4b15042 100644 --- a/crates/fbuild-build/src/renesas/renesas_linker.rs +++ b/crates/fbuild-build/src/renesas/renesas_linker.rs @@ -152,6 +152,18 @@ impl Linker for RenesasLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/sam/sam_linker.rs b/crates/fbuild-build/src/sam/sam_linker.rs index 4e9ee332..50fa09cc 100644 --- a/crates/fbuild-build/src/sam/sam_linker.rs +++ b/crates/fbuild-build/src/sam/sam_linker.rs @@ -168,6 +168,18 @@ impl Linker for SamLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/silabs/silabs_linker.rs b/crates/fbuild-build/src/silabs/silabs_linker.rs index 04590f65..6c164924 100644 --- a/crates/fbuild-build/src/silabs/silabs_linker.rs +++ b/crates/fbuild-build/src/silabs/silabs_linker.rs @@ -155,6 +155,18 @@ impl Linker for SilabsLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path, diff --git a/crates/fbuild-build/src/teensy/teensy_linker.rs b/crates/fbuild-build/src/teensy/teensy_linker.rs index 08f857ed..8766e5ba 100644 --- a/crates/fbuild-build/src/teensy/teensy_linker.rs +++ b/crates/fbuild-build/src/teensy/teensy_linker.rs @@ -153,6 +153,18 @@ impl Linker for TeensyLinker { &self.size_path } + fn ar_tool_path(&self) -> Option<&Path> { + Some(&self.ar_path) + } + + fn objcopy_tool_path(&self) -> Option<&Path> { + Some(&self.objcopy_path) + } + + fn link_driver_path(&self) -> Option<&Path> { + Some(&self.gcc_path) + } + fn report_size(&self, elf_path: &Path) -> Result { crate::linker::LinkerBase::report_size( &self.size_path,