diff --git a/crates/fbuild-build-arm/src/apollo3/orchestrator.rs b/crates/fbuild-build-arm/src/apollo3/orchestrator.rs index 4e35136d8..e8926eb6d 100644 --- a/crates/fbuild-build-arm/src/apollo3/orchestrator.rs +++ b/crates/fbuild-build-arm/src/apollo3/orchestrator.rs @@ -17,6 +17,10 @@ use std::time::Instant; use fbuild_core::{Platform, Result}; +use crate::build_fingerprint::{ + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathPersistInputs, + expected_fast_path_artifacts, stable_hash_json, +}; use crate::compile_database::TargetArchitecture; use crate::generic_arm::{ArmCompiler, ArmLinker}; use crate::pipeline; @@ -25,6 +29,13 @@ use crate::{BuildOrchestrator, BuildParams, BuildResult, SourceScanner}; /// Apollo3 platform build orchestrator. pub struct Apollo3Orchestrator; +fn profile_label(profile: fbuild_core::BuildProfile) -> &'static str { + match profile { + fbuild_core::BuildProfile::Release => "release", + fbuild_core::BuildProfile::Quick => "quick", + } +} + #[async_trait::async_trait] impl BuildOrchestrator for Apollo3Orchestrator { fn platform(&self) -> Platform { @@ -75,6 +86,65 @@ impl BuildOrchestrator for Apollo3Orchestrator { let framework_dir = fbuild_packages::Package::ensure_installed(&framework).await?; tracing::info!("Apollo3 cores at {}", framework_dir.display()); + let build_dir = &ctx.build_dir; + let metadata_hash = stable_hash_json(&CoreFingerprintMetadata { + version: crate::build_fingerprint::BUILD_FINGERPRINT_VERSION, + env_name: params.env_name.clone(), + profile: profile_label(params.profile).to_string(), + board_name: ctx.board.name.clone(), + board_mcu: ctx.board.mcu.clone(), + board_define: ctx.board.board.clone(), + board_core: ctx.board.core.clone(), + board_f_cpu: ctx.board.f_cpu.clone(), + board_extra_flags: ctx.board.extra_flags.clone(), + board_ldscript: ctx.board.ldscript.clone(), + board_variant: Some(ctx.board.variant.clone()), + platform: "apollo3".to_string(), + max_flash: ctx.board.max_flash, + max_ram: ctx.board.max_ram, + eh_frame_policy: Some(match eh_frame_policy { + crate::eh_frame_policy::EhFramePolicy::Strip => "strip".to_string(), + crate::eh_frame_policy::EhFramePolicy::Preserve => "preserve".to_string(), + }), + extra: None, + })?; + let (fast_elf, [fast_bin], fast_compile_db) = + expected_fast_path_artifacts(build_dir, ¶ms.project_dir, ["firmware.bin"]); + let fast_path = FastPathContract::for_project_outputs( + build_dir, + ¶ms.project_dir, + [fast_elf.clone(), fast_bin.clone(), fast_compile_db.clone()], + ); + let compiler_cache: Option = None; + + if !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + let inputs = FastPathCheckInputs { + metadata_hash: &metadata_hash, + extra_artifact_ok: None, + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }; + if let Some(hit) = crate::build_fingerprint::fast_path_check(&fast_path, &inputs)? { + let elapsed = start.elapsed().as_secs_f64(); + return Ok(crate::build_fingerprint::assemble_fast_path_result( + hit, + ctx.build_log, + crate::build_fingerprint::FastPathResultInputs { + platform_label: "APOLLO3", + mcu: &ctx.board.mcu, + env_name: ¶ms.env_name, + firmware_path: fast_bin, + elf_path: fast_elf, + compile_database_path: fast_compile_db, + elapsed, + }, + )); + } + } + // 5. Scan sources (core + variant) let core_dir = framework.get_core_dir(&ctx.board.core); let variant_dir = framework.get_variant_dir(&ctx.board.variant); @@ -276,7 +346,7 @@ impl BuildOrchestrator for Apollo3Orchestrator { }; // 10. Run shared sequential build pipeline - pipeline::run_sequential_build_with_libs( + let result = pipeline::run_sequential_build_with_libs( &compiler, &linker, ctx, @@ -288,7 +358,25 @@ impl BuildOrchestrator for Apollo3Orchestrator { "APOLLO3", start, ) - .await + .await?; + + if result.success + && !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + crate::build_fingerprint::persist_fast_path_success( + &fast_path, + &FastPathPersistInputs { + metadata_hash: &metadata_hash, + size_info: result.size_info.clone(), + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }, + ); + } + + Ok(result) } } diff --git a/crates/fbuild-build-arm/src/nxplpc/orchestrator.rs b/crates/fbuild-build-arm/src/nxplpc/orchestrator.rs index 72e21c66e..f2162e7e3 100644 --- a/crates/fbuild-build-arm/src/nxplpc/orchestrator.rs +++ b/crates/fbuild-build-arm/src/nxplpc/orchestrator.rs @@ -1,4 +1,4 @@ -//! NXP LPC8xx build orchestrator — Stage 2 of #487. +//! NXP LPC8xx build orchestrator — Stage 2 of #487. //! //! Compiles user sketch sources (.ino → .cpp + .c + .cpp + .S) together //! with the per-MCU startup `.S` and the hand-rolled Arduino `main.cpp` @@ -21,6 +21,10 @@ use std::time::Instant; use fbuild_core::{FbuildError, Platform, Result}; +use crate::build_fingerprint::{ + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathPersistInputs, + expected_fast_path_artifacts, stable_hash_json, +}; use crate::compile_database::TargetArchitecture; use crate::flag_overlay::apply_overlay_flags; use crate::generic_arm::{ArmCompiler, ArmLinker}; @@ -80,6 +84,13 @@ fn collect_compilable_sources(dir: &std::path::Path) -> Result> { /// NXP LPC8xx (Cortex-M0+) build orchestrator. pub struct NxpLpcOrchestrator; +fn profile_label(profile: fbuild_core::BuildProfile) -> &'static str { + match profile { + fbuild_core::BuildProfile::Release => "release", + fbuild_core::BuildProfile::Quick => "quick", + } +} + #[async_trait::async_trait] impl BuildOrchestrator for NxpLpcOrchestrator { fn platform(&self) -> Platform { @@ -170,6 +181,68 @@ impl BuildOrchestrator for NxpLpcOrchestrator { ))); } + let build_dir = &ctx.build_dir; + let metadata_hash = stable_hash_json(&CoreFingerprintMetadata { + version: crate::build_fingerprint::BUILD_FINGERPRINT_VERSION, + env_name: params.env_name.clone(), + profile: profile_label(params.profile).to_string(), + board_name: ctx.board.name.clone(), + board_mcu: ctx.board.mcu.clone(), + board_define: ctx.board.board.clone(), + board_core: ctx.board.core.clone(), + board_f_cpu: ctx.board.f_cpu.clone(), + board_extra_flags: ctx.board.extra_flags.clone(), + board_ldscript: ctx.board.ldscript.clone(), + board_variant: Some(ctx.board.variant.clone()), + platform: "nxplpc".to_string(), + max_flash: ctx.board.max_flash, + max_ram: ctx.board.max_ram, + eh_frame_policy: Some(match eh_frame_policy { + crate::eh_frame_policy::EhFramePolicy::Strip => "strip".to_string(), + crate::eh_frame_policy::EhFramePolicy::Preserve => "preserve".to_string(), + }), + extra: Some(std::collections::BTreeMap::from([( + "lpc_family".to_string(), + lpc_family.to_string(), + )])), + })?; + let (fast_elf, [fast_bin], fast_compile_db) = + expected_fast_path_artifacts(build_dir, ¶ms.project_dir, ["firmware.bin"]); + let fast_path = FastPathContract::for_project_outputs( + build_dir, + ¶ms.project_dir, + [fast_elf.clone(), fast_bin.clone(), fast_compile_db.clone()], + ); + let compiler_cache: Option = None; + + if !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + let inputs = FastPathCheckInputs { + metadata_hash: &metadata_hash, + extra_artifact_ok: None, + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }; + if let Some(hit) = crate::build_fingerprint::fast_path_check(&fast_path, &inputs)? { + let elapsed = start.elapsed().as_secs_f64(); + return Ok(crate::build_fingerprint::assemble_fast_path_result( + hit, + ctx.build_log, + crate::build_fingerprint::FastPathResultInputs { + platform_label: "NXPLPC", + mcu: &ctx.board.mcu, + env_name: ¶ms.env_name, + firmware_path: fast_bin, + elf_path: fast_elf, + compile_database_path: fast_compile_db, + elapsed, + }, + )); + } + } + // 6. Scan user sources, then add the vendored core sources // (framework main(), startup, wiring, HardwareSerial, SPI, ...) // plus the board variant glue as "core" sources. @@ -303,7 +376,7 @@ impl BuildOrchestrator for NxpLpcOrchestrator { .await?; // 11. Run the shared sequential build pipeline. - pipeline::run_sequential_build_with_libs( + let result = pipeline::run_sequential_build_with_libs( &compiler, &linker, ctx, @@ -315,7 +388,25 @@ impl BuildOrchestrator for NxpLpcOrchestrator { "NXPLPC", start, ) - .await + .await?; + + if result.success + && !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + crate::build_fingerprint::persist_fast_path_success( + &fast_path, + &FastPathPersistInputs { + metadata_hash: &metadata_hash, + size_info: result.size_info.clone(), + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }, + ); + } + + Ok(result) } } diff --git a/crates/fbuild-build-arm/src/silabs/orchestrator.rs b/crates/fbuild-build-arm/src/silabs/orchestrator.rs index 380ae8413..a8968830c 100644 --- a/crates/fbuild-build-arm/src/silabs/orchestrator.rs +++ b/crates/fbuild-build-arm/src/silabs/orchestrator.rs @@ -1,9 +1,13 @@ -//! Silicon Labs build orchestrator. +//! Silicon Labs build orchestrator. use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::time::Instant; +use crate::build_fingerprint::{ + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathPersistInputs, + expected_fast_path_artifacts, stable_hash_json, +}; use crate::compile_database::TargetArchitecture; use crate::pipeline; use crate::{BuildOrchestrator, BuildParams, BuildResult, SourceScanner}; @@ -14,6 +18,13 @@ use super::{SilabsCompiler, SilabsLinker}; /// Silicon Labs platform build orchestrator. pub struct SilabsOrchestrator; +fn profile_label(profile: fbuild_core::BuildProfile) -> &'static str { + match profile { + fbuild_core::BuildProfile::Release => "release", + fbuild_core::BuildProfile::Quick => "quick", + } +} + #[async_trait::async_trait] impl BuildOrchestrator for SilabsOrchestrator { fn platform(&self) -> Platform { @@ -69,6 +80,65 @@ impl BuildOrchestrator for SilabsOrchestrator { ))); } + let build_dir = &ctx.build_dir; + let metadata_hash = stable_hash_json(&CoreFingerprintMetadata { + version: crate::build_fingerprint::BUILD_FINGERPRINT_VERSION, + env_name: params.env_name.clone(), + profile: profile_label(params.profile).to_string(), + board_name: ctx.board.name.clone(), + board_mcu: ctx.board.mcu.clone(), + board_define: ctx.board.board.clone(), + board_core: ctx.board.core.clone(), + board_f_cpu: ctx.board.f_cpu.clone(), + board_extra_flags: ctx.board.extra_flags.clone(), + board_ldscript: ctx.board.ldscript.clone(), + board_variant: Some(ctx.board.variant.clone()), + platform: "silabs".to_string(), + max_flash: ctx.board.max_flash, + max_ram: ctx.board.max_ram, + eh_frame_policy: None, + extra: Some(std::collections::BTreeMap::from([( + "protocol_stack".to_string(), + protocol_stack.clone(), + )])), + })?; + let (fast_elf, [fast_bin], fast_compile_db) = + expected_fast_path_artifacts(build_dir, ¶ms.project_dir, ["firmware.bin"]); + let fast_path = FastPathContract::for_project_outputs( + build_dir, + ¶ms.project_dir, + [fast_elf.clone(), fast_bin.clone(), fast_compile_db.clone()], + ); + let compiler_cache: Option = None; + + if !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + let inputs = FastPathCheckInputs { + metadata_hash: &metadata_hash, + extra_artifact_ok: None, + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }; + if let Some(hit) = crate::build_fingerprint::fast_path_check(&fast_path, &inputs)? { + let elapsed = start.elapsed().as_secs_f64(); + return Ok(crate::build_fingerprint::assemble_fast_path_result( + hit, + ctx.build_log, + crate::build_fingerprint::FastPathResultInputs { + platform_label: "Silicon Labs", + mcu: &ctx.board.mcu, + env_name: ¶ms.env_name, + firmware_path: fast_bin, + elf_path: fast_elf, + compile_database_path: fast_compile_db, + elapsed, + }, + )); + } + } + let scanner = SourceScanner::new(&ctx.src_dir, &ctx.src_build_dir); let mut sources = scanner.scan_all_filtered(Some(&core_dir), None, ctx.source_filter.as_deref())?; @@ -156,7 +226,7 @@ impl BuildOrchestrator for SilabsOrchestrator { jobs: crate::parallel::effective_jobs(params.jobs), compiler_cache: None, }; - pipeline::run_sequential_build_with_libs( + let result = pipeline::run_sequential_build_with_libs( &compiler, &linker, ctx, @@ -168,7 +238,25 @@ impl BuildOrchestrator for SilabsOrchestrator { "Silicon Labs", start, ) - .await + .await?; + + if result.success + && !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + crate::build_fingerprint::persist_fast_path_success( + &fast_path, + &FastPathPersistInputs { + metadata_hash: &metadata_hash, + size_info: result.size_info.clone(), + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }, + ); + } + + Ok(result) } } diff --git a/crates/fbuild-build-arm/src/stm32/orchestrator/mod.rs b/crates/fbuild-build-arm/src/stm32/orchestrator/mod.rs index 939ee7b9a..344fb199a 100644 --- a/crates/fbuild-build-arm/src/stm32/orchestrator/mod.rs +++ b/crates/fbuild-build-arm/src/stm32/orchestrator/mod.rs @@ -31,6 +31,10 @@ use std::time::Instant; use fbuild_core::{Platform, Result}; use fbuild_packages::{Framework, Toolchain}; +use crate::build_fingerprint::{ + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathPersistInputs, + expected_fast_path_artifacts, stable_hash_json, +}; use crate::compile_database::TargetArchitecture; use crate::framework_libs::{ library_select_kv_store, resolve_framework_library_sources_active_declared, @@ -48,6 +52,13 @@ use self::variant_files::{keep_variant_source, select_variant_files}; /// STM32 platform build orchestrator. pub struct Stm32Orchestrator; +fn profile_label(profile: fbuild_core::BuildProfile) -> &'static str { + match profile { + fbuild_core::BuildProfile::Release => "release", + fbuild_core::BuildProfile::Quick => "quick", + } +} + #[async_trait::async_trait] impl BuildOrchestrator for Stm32Orchestrator { fn platform(&self) -> Platform { @@ -96,6 +107,65 @@ impl BuildOrchestrator for Stm32Orchestrator { let framework_dir = fbuild_packages::Package::ensure_installed(&framework).await?; tracing::info!("STM32 cores at {}", framework_dir.display()); + let build_dir = &ctx.build_dir; + let metadata_hash = stable_hash_json(&CoreFingerprintMetadata { + version: crate::build_fingerprint::BUILD_FINGERPRINT_VERSION, + env_name: params.env_name.clone(), + profile: profile_label(params.profile).to_string(), + board_name: ctx.board.name.clone(), + board_mcu: ctx.board.mcu.clone(), + board_define: ctx.board.board.clone(), + board_core: ctx.board.core.clone(), + board_f_cpu: ctx.board.f_cpu.clone(), + board_extra_flags: ctx.board.extra_flags.clone(), + board_ldscript: ctx.board.ldscript.clone(), + board_variant: Some(ctx.board.variant.clone()), + platform: "stm32".to_string(), + max_flash: ctx.board.max_flash, + max_ram: ctx.board.max_ram, + eh_frame_policy: Some(match eh_frame_policy { + crate::eh_frame_policy::EhFramePolicy::Strip => "strip".to_string(), + crate::eh_frame_policy::EhFramePolicy::Preserve => "preserve".to_string(), + }), + extra: None, + })?; + let (fast_elf, [fast_hex], fast_compile_db) = + expected_fast_path_artifacts(build_dir, ¶ms.project_dir, ["firmware.hex"]); + let fast_path = FastPathContract::for_project_outputs( + build_dir, + ¶ms.project_dir, + [fast_elf.clone(), fast_hex.clone(), fast_compile_db.clone()], + ); + let compiler_cache: Option = None; + + if !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + let inputs = FastPathCheckInputs { + metadata_hash: &metadata_hash, + extra_artifact_ok: None, + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }; + if let Some(hit) = crate::build_fingerprint::fast_path_check(&fast_path, &inputs)? { + let elapsed = start.elapsed().as_secs_f64(); + return Ok(crate::build_fingerprint::assemble_fast_path_result( + hit, + ctx.build_log, + crate::build_fingerprint::FastPathResultInputs { + platform_label: "STM32", + mcu: &ctx.board.mcu, + env_name: ¶ms.env_name, + firmware_path: fast_hex, + elf_path: fast_elf, + compile_database_path: fast_compile_db, + elapsed, + }, + )); + } + } + // 5. Scan sources (core + variant) // STM32duino uses "arduino" as its core directory name, even though // the board JSON says core = "stm32". Map it here. @@ -336,7 +406,7 @@ impl BuildOrchestrator for Stm32Orchestrator { }; // 9. Run shared sequential build pipeline - pipeline::run_sequential_build_with_libs( + let result = pipeline::run_sequential_build_with_libs( &compiler, &linker, ctx, @@ -348,7 +418,25 @@ impl BuildOrchestrator for Stm32Orchestrator { "STM32", start, ) - .await + .await?; + + if result.success + && !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + crate::build_fingerprint::persist_fast_path_success( + &fast_path, + &FastPathPersistInputs { + metadata_hash: &metadata_hash, + size_info: result.size_info.clone(), + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }, + ); + } + + Ok(result) } } diff --git a/crates/fbuild-build-engine/src/build_fingerprint/fast_path.rs b/crates/fbuild-build-engine/src/build_fingerprint/fast_path.rs index ce24e39d6..fe747f942 100644 --- a/crates/fbuild-build-engine/src/build_fingerprint/fast_path.rs +++ b/crates/fbuild-build-engine/src/build_fingerprint/fast_path.rs @@ -23,9 +23,11 @@ //! [`PersistedBuildFingerprint`] so the caller only has to re-use //! fields, not reload them. +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use fbuild_core::{BuildLog, Result, SizeInfo}; +use serde::Serialize; use super::{ BUILD_FINGERPRINT_VERSION, PersistedBuildFingerprint, WatchSetStampCache, @@ -352,6 +354,43 @@ pub fn persist_fast_path_success(contract: &FastPathContract, inputs: &FastPathP } } +/// Shared fingerprint metadata for all platform orchestrators. +/// +/// Replace the 7 original per-platform metadata structs that had silently +/// diverged. Every orchestrator hashes this struct via [`super::stable_hash_json`] +/// to decide whether a prior build's artifacts are still valid. +/// +/// Platform-specific fields (flash mode, lpc-family, etc.) go into `extra`. +/// FastLED/fbuild#1288. +#[derive(Debug, Serialize)] +pub struct CoreFingerprintMetadata { + pub version: u32, + pub env_name: String, + pub profile: String, + pub board_name: String, + pub board_mcu: String, + pub board_define: String, + pub board_core: String, + pub board_f_cpu: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub board_extra_flags: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub board_ldscript: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub board_variant: Option, + pub platform: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_flash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_ram: Option, + /// `"strip"` / `"preserve"`. Absent when the platform doesn't compute it. + #[serde(skip_serializing_if = "Option::is_none")] + pub eh_frame_policy: Option, + /// Platform-specific key-value pairs. + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option>, +} + /// Inputs to [`assemble_fast_path_result`]. pub struct FastPathResultInputs<'a> { /// Platform label for log/result messages (e.g. `"AVR"`, `"ESP32"`). diff --git a/crates/fbuild-build-engine/src/build_fingerprint/mod.rs b/crates/fbuild-build-engine/src/build_fingerprint/mod.rs index e314ad5b8..5411cea7c 100644 --- a/crates/fbuild-build-engine/src/build_fingerprint/mod.rs +++ b/crates/fbuild-build-engine/src/build_fingerprint/mod.rs @@ -3,9 +3,9 @@ pub mod fast_path; pub use fast_path::{ - FastPathCheckInputs, FastPathContract, FastPathHit, FastPathInputs, FastPathPersistInputs, - FastPathResultInputs, assemble_fast_path_result, expected_fast_path_artifacts, fast_path_check, - fast_path_watch, persist_fast_path_success, + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathHit, FastPathInputs, + FastPathPersistInputs, FastPathResultInputs, assemble_fast_path_result, + expected_fast_path_artifacts, fast_path_check, fast_path_watch, persist_fast_path_success, }; use std::collections::HashMap; diff --git a/crates/fbuild-build-esp/src/esp8266/orchestrator.rs b/crates/fbuild-build-esp/src/esp8266/orchestrator.rs index df7ae905e..69c53417d 100644 --- a/crates/fbuild-build-esp/src/esp8266/orchestrator.rs +++ b/crates/fbuild-build-esp/src/esp8266/orchestrator.rs @@ -1,4 +1,4 @@ -//! ESP8266 build orchestrator — wires together config, packages, compiler, linker. +//! ESP8266 build orchestrator — wires together config, packages, compiler, linker. //! //! Build phases: //! 1. Parse platformio.ini @@ -17,6 +17,10 @@ use std::time::Instant; use fbuild_core::{Platform, Result}; use fbuild_packages::Framework as _; +use crate::build_fingerprint::{ + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathPersistInputs, + expected_fast_path_artifacts, stable_hash_json, +}; use crate::compile_database::TargetArchitecture; use crate::pipeline; use crate::{BuildOrchestrator, BuildParams, BuildResult, SourceScanner}; @@ -28,6 +32,13 @@ use super::mcu_config::get_esp8266_config; /// ESP8266 platform build orchestrator. pub struct Esp8266Orchestrator; +fn profile_label(profile: fbuild_core::BuildProfile) -> &'static str { + match profile { + fbuild_core::BuildProfile::Release => "release", + fbuild_core::BuildProfile::Quick => "quick", + } +} + #[async_trait::async_trait] impl BuildOrchestrator for Esp8266Orchestrator { fn platform(&self) -> Platform { @@ -93,6 +104,83 @@ impl BuildOrchestrator for Esp8266Orchestrator { let mut mcu_config = get_esp8266_config()?; apply_esp8266_board_props(&board_props, &mut mcu_config); + // Compute flash_freq early for the fast-path fingerprint (also used by + // the linker constructor below). + let f_for_image = ctx + .board + .f_image + .as_deref() + .or(ctx.board.f_flash.as_deref()); + let flash_freq = crate::esp32::esp32_linker::f_flash_to_esptool_freq( + f_for_image, + &mcu_config.esptool.default_flash_freq, + ); + + let build_dir = &ctx.build_dir; + let metadata_hash = stable_hash_json(&CoreFingerprintMetadata { + version: crate::build_fingerprint::BUILD_FINGERPRINT_VERSION, + env_name: params.env_name.clone(), + profile: profile_label(params.profile).to_string(), + board_name: ctx.board.name.clone(), + board_mcu: ctx.board.mcu.clone(), + board_define: ctx.board.board.clone(), + board_core: ctx.board.core.clone(), + board_f_cpu: ctx.board.f_cpu.clone(), + board_extra_flags: ctx.board.extra_flags.clone(), + board_ldscript: ctx.board.ldscript.clone(), + board_variant: Some(ctx.board.variant.clone()), + platform: "esp8266".to_string(), + max_flash: ctx.board.max_flash, + max_ram: ctx.board.max_ram, + eh_frame_policy: Some(match eh_frame_policy { + crate::eh_frame_policy::EhFramePolicy::Strip => "strip".to_string(), + crate::eh_frame_policy::EhFramePolicy::Preserve => "preserve".to_string(), + }), + extra: Some(std::collections::BTreeMap::from([ + ( + "flash_mode".to_string(), + ctx.board.flash_mode.clone().unwrap_or_default(), + ), + ("flash_freq".to_string(), flash_freq.clone()), + ])), + })?; + let (fast_elf, [fast_bin], fast_compile_db) = + expected_fast_path_artifacts(build_dir, ¶ms.project_dir, ["firmware.bin"]); + let fast_path = FastPathContract::for_project_outputs( + build_dir, + ¶ms.project_dir, + [fast_elf.clone(), fast_bin.clone(), fast_compile_db.clone()], + ); + let compiler_cache: Option = None; + + if !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + let inputs = FastPathCheckInputs { + metadata_hash: &metadata_hash, + extra_artifact_ok: None, + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }; + if let Some(hit) = crate::build_fingerprint::fast_path_check(&fast_path, &inputs)? { + let elapsed = start.elapsed().as_secs_f64(); + return Ok(crate::build_fingerprint::assemble_fast_path_result( + hit, + ctx.build_log, + crate::build_fingerprint::FastPathResultInputs { + platform_label: "ESP8266", + mcu: &ctx.board.mcu, + env_name: ¶ms.env_name, + firmware_path: fast_bin, + elf_path: fast_elf, + compile_database_path: fast_compile_db, + elapsed, + }, + )); + } + } + // 6. Scan sources let scanner = SourceScanner::new(&ctx.src_dir, &ctx.src_build_dir); let variant_dir_opt = if variant_dir.exists() { @@ -169,17 +257,7 @@ impl BuildOrchestrator for Esp8266Orchestrator { let sdk_ld_dir = framework.get_sdk_ld_dir(); let linker_scripts = crate::linker::LinkerScripts::single(sdk_ld_dir.clone(), ldscript); - // Prefer f_image over f_flash for esptool frequency (see ESP32 orchestrator comment) - let f_for_image = ctx - .board - .f_image - .as_deref() - .or(ctx.board.f_flash.as_deref()); - let flash_freq = crate::esp32::esp32_linker::f_flash_to_esptool_freq( - f_for_image, - &mcu_config.esptool.default_flash_freq, - ); - + // flash_freq was computed above for the fast-path fingerprint; reuse here. let sdk_name = esp8266_sdk_name(&mcu_config).to_string(); let linker = Esp8266Linker::new( toolchain.get_gcc_path(), @@ -222,7 +300,7 @@ impl BuildOrchestrator for Esp8266Orchestrator { }; // 9. Run shared sequential build pipeline - pipeline::run_sequential_build_with_libs( + let result = pipeline::run_sequential_build_with_libs( &compiler, &linker, ctx, @@ -234,7 +312,25 @@ impl BuildOrchestrator for Esp8266Orchestrator { "ESP8266", start, ) - .await + .await?; + + if result.success + && !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + crate::build_fingerprint::persist_fast_path_success( + &fast_path, + &FastPathPersistInputs { + metadata_hash: &metadata_hash, + size_info: result.size_info.clone(), + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }, + ); + } + + Ok(result) } } diff --git a/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs b/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs index 2785e1ac4..0ce88cad6 100644 --- a/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs +++ b/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs @@ -17,6 +17,10 @@ use std::time::Instant; use fbuild_core::{Platform, Result}; +use crate::build_fingerprint::{ + CoreFingerprintMetadata, FastPathCheckInputs, FastPathContract, FastPathPersistInputs, + expected_fast_path_artifacts, stable_hash_json, +}; use crate::compile_database::TargetArchitecture; use crate::pipeline; use crate::{BuildOrchestrator, BuildParams, BuildResult, SourceScanner}; @@ -27,6 +31,13 @@ use super::ch32v_linker::Ch32vLinker; /// CH32V platform build orchestrator. pub struct Ch32vOrchestrator; +fn profile_label(profile: fbuild_core::BuildProfile) -> &'static str { + match profile { + fbuild_core::BuildProfile::Release => "release", + fbuild_core::BuildProfile::Quick => "quick", + } +} + #[async_trait::async_trait] impl BuildOrchestrator for Ch32vOrchestrator { fn platform(&self) -> Platform { @@ -101,6 +112,65 @@ impl BuildOrchestrator for Ch32vOrchestrator { let core_dir = framework.get_core_dir(&ctx.board.core); let variant_dir = resolve_variant_dir(&framework_dir, &ctx.board.variant, &system_series); + let build_dir = &ctx.build_dir; + let metadata_hash = stable_hash_json(&CoreFingerprintMetadata { + version: crate::build_fingerprint::BUILD_FINGERPRINT_VERSION, + env_name: params.env_name.clone(), + profile: profile_label(params.profile).to_string(), + board_name: ctx.board.name.clone(), + board_mcu: ctx.board.mcu.clone(), + board_define: ctx.board.board.clone(), + board_core: ctx.board.core.clone(), + board_f_cpu: ctx.board.f_cpu.clone(), + board_extra_flags: ctx.board.extra_flags.clone(), + board_ldscript: ctx.board.ldscript.clone(), + board_variant: Some(ctx.board.variant.clone()), + platform: "ch32v".to_string(), + max_flash: ctx.board.max_flash, + max_ram: ctx.board.max_ram, + eh_frame_policy: None, + extra: Some(std::collections::BTreeMap::from([( + "series".to_string(), + series.clone(), + )])), + })?; + let (fast_elf, [fast_bin], fast_compile_db) = + expected_fast_path_artifacts(build_dir, ¶ms.project_dir, ["firmware.bin"]); + let fast_path = FastPathContract::for_project_outputs( + build_dir, + ¶ms.project_dir, + [fast_elf.clone(), fast_bin.clone(), fast_compile_db.clone()], + ); + let compiler_cache: Option = None; + + if !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + let inputs = FastPathCheckInputs { + metadata_hash: &metadata_hash, + extra_artifact_ok: None, + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }; + if let Some(hit) = crate::build_fingerprint::fast_path_check(&fast_path, &inputs)? { + let elapsed = start.elapsed().as_secs_f64(); + return Ok(crate::build_fingerprint::assemble_fast_path_result( + hit, + ctx.build_log, + crate::build_fingerprint::FastPathResultInputs { + platform_label: "CH32V", + mcu: &ctx.board.mcu, + env_name: ¶ms.env_name, + firmware_path: fast_bin, + elf_path: fast_elf, + compile_database_path: fast_compile_db, + elapsed, + }, + )); + } + } + let scanner = SourceScanner::new(&ctx.src_dir, &ctx.src_build_dir); let sources = scanner.scan_all_filtered( Some(&core_dir), @@ -236,7 +306,7 @@ impl BuildOrchestrator for Ch32vOrchestrator { }; // 9. Run shared sequential build pipeline - pipeline::run_sequential_build_with_libs( + let result = pipeline::run_sequential_build_with_libs( &compiler, &linker, ctx, @@ -248,7 +318,25 @@ impl BuildOrchestrator for Ch32vOrchestrator { "CH32V", start, ) - .await + .await?; + + if result.success + && !params.compiledb_only + && !params.symbol_analysis + && params.symbol_analysis_path.is_none() + { + crate::build_fingerprint::persist_fast_path_success( + &fast_path, + &FastPathPersistInputs { + metadata_hash: &metadata_hash, + size_info: result.size_info.clone(), + watch_set_cache: params.watch_set_cache.as_deref(), + compiler_cache: compiler_cache.as_deref(), + }, + ); + } + + Ok(result) } }