diff --git a/ci/platform_boundary_ledger.tsv b/ci/platform_boundary_ledger.tsv index 16f16568..611cd733 100644 --- a/ci/platform_boundary_ledger.tsv +++ b/ci/platform_boundary_ledger.tsv @@ -22,32 +22,8 @@ crates/fbuild-config/src/bin/enrich_boards.rs attr_cfg #[cfg(windows)] 0 host ho crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs attr_cfg #[cfg(windows)] 0 host_executable host_artifact_policy crates/fbuild-daemon/src/handlers/emulator/tests_process.rs attr_cfg #[cfg(not(windows))] 0 host_executable host_artifact_policy crates/fbuild-daemon/src/handlers/emulator/tests_process.rs attr_cfg #[cfg(windows)] 0 host_executable host_artifact_policy -crates/fbuild-deploy/src/lpc.rs attr_cfg #[cfg(not(target_os=))] 0 device host_mechanic -crates/fbuild-deploy/src/lpc.rs attr_cfg #[cfg(not(windows))] 0 device host_mechanic -crates/fbuild-deploy/src/lpc.rs attr_cfg #[cfg(target_os=)] 0 device host_mechanic -crates/fbuild-deploy/src/lpc.rs attr_cfg #[cfg(windows)] 0 device host_mechanic -crates/fbuild-deploy/src/lpc_debugger_reflash.rs attr_cfg #[cfg(target_os=)] 0 device host_mechanic -crates/fbuild-deploy/src/probe_rs.rs attr_cfg #[cfg(windows)] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg #[cfg(any(target_os=,test))] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg #[cfg(any(target_os=,test))] 1 device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg #[cfg(not(target_os=))] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg #[cfg(target_os=)] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(any(windows,test))] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(any(windows,test))] 1 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(any(windows,test))] 2 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(any(windows,test))] 3 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(any(windows,test))] 4 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(any(windows,test))] 5 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(not(windows))] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(not(windows))] 1 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(windows)] 0 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg #[cfg(windows)] 1 device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs native_path std::os::windows::ffi::OsStrExt 0 device host_mechanic crates/fbuild-library/src/library/library_spec.rs attr_cfg #[cfg(windows)] 0 host host_mechanic crates/fbuild-paths/src/dev_daemon_namespace.rs native_path std::env::current_exe 0 host_executable host_mechanic -crates/fbuild-serial/src/ports.rs attr_cfg #[cfg(target_os=)] 0 device host_mechanic -crates/fbuild-serial/src/ports.rs attr_cfg #[cfg(target_os=)] 1 device host_mechanic -crates/fbuild-serial/src/ports.rs attr_cfg #[cfg(target_os=)] 2 device host_mechanic crates/fbuild-toolchain/src/toolchain/esp_qemu.rs attr_cfg #[cfg(not(target_os=))] 0 host_executable host_mechanic crates/fbuild-toolchain/src/toolchain/esp_qemu.rs attr_cfg #[cfg(not(windows))] 0 host_executable host_mechanic crates/fbuild-toolchain/src/toolchain/esp_qemu.rs attr_cfg #[cfg(not(windows))] 1 host_executable host_mechanic diff --git a/ci/platform_boundary_research.tsv b/ci/platform_boundary_research.tsv index a0fed0ea..5a73e790 100644 --- a/ci/platform_boundary_research.tsv +++ b/ci/platform_boundary_research.tsv @@ -37,7 +37,7 @@ crates/fbuild-core/src/platform/linux/ipc.rs 2 native_path interprocess::local_s crates/fbuild-core/src/platform/linux/ipc.rs 3 native_path interprocess::os::unix ipc host_mechanic crates/fbuild-core/src/platform/linux/ipc.rs 4 native_path socket2:: ipc host_mechanic crates/fbuild-core/src/platform/linux/ipc.rs 66 native_path std::os::unix::fs::PermissionsExt ipc host_mechanic -crates/fbuild-core/src/platform/linux/mod.rs 12 compile_host_fact std::env::consts::ARCH host host_mechanic +crates/fbuild-core/src/platform/linux/mod.rs 13 compile_host_fact std::env::consts::ARCH host host_mechanic crates/fbuild-core/src/platform/linux/process.rs 1 native_path std::os::unix::process::ExitStatusExt process host_mechanic crates/fbuild-core/src/platform/macos/fs.rs 2 native_path std::os::unix::fs::PermissionsExt fs host_mechanic crates/fbuild-core/src/platform/macos/fs.rs 40 native_path std::os::unix::fs::symlink fs host_mechanic @@ -50,13 +50,13 @@ crates/fbuild-core/src/platform/macos/ipc.rs 2 native_path interprocess::local_s crates/fbuild-core/src/platform/macos/ipc.rs 3 native_path socket2:: ipc host_mechanic crates/fbuild-core/src/platform/macos/ipc.rs 19 native_path std::os::unix::fs::PermissionsExt ipc host_mechanic crates/fbuild-core/src/platform/macos/ipc.rs 72 native_path std::os::unix::fs::PermissionsExt ipc host_mechanic -crates/fbuild-core/src/platform/macos/mod.rs 12 compile_host_fact std::env::consts::ARCH host host_mechanic +crates/fbuild-core/src/platform/macos/mod.rs 13 compile_host_fact std::env::consts::ARCH host host_mechanic crates/fbuild-core/src/platform/macos/process.rs 1 native_path std::os::unix::process::ExitStatusExt process host_mechanic crates/fbuild-core/src/platform/windows/device.rs 18 native_path windows_sys:: process host_mechanic -crates/fbuild-core/src/platform/windows/device.rs 26 native_path windows_sys:: process host_mechanic -crates/fbuild-core/src/platform/windows/device.rs 29 native_path windows_sys:: process host_mechanic +crates/fbuild-core/src/platform/windows/device.rs 27 native_path windows_sys:: process host_mechanic crates/fbuild-core/src/platform/windows/device.rs 30 native_path windows_sys:: process host_mechanic -crates/fbuild-core/src/platform/windows/device.rs 34 native_path windows_sys:: process host_mechanic +crates/fbuild-core/src/platform/windows/device.rs 31 native_path windows_sys:: process host_mechanic +crates/fbuild-core/src/platform/windows/device.rs 35 native_path windows_sys:: process host_mechanic crates/fbuild-core/src/platform/windows/fs.rs 2 native_path std::os::windows::ffi::OsStrExt process host_mechanic crates/fbuild-core/src/platform/windows/fs.rs 3 native_path std::os::windows::fs fs host_mechanic crates/fbuild-core/src/platform/windows/fs.rs 4 native_path std::os::windows::io::AsRawHandle process host_mechanic @@ -70,7 +70,7 @@ crates/fbuild-core/src/platform/windows/ipc.rs 3 native_path interprocess::os::w crates/fbuild-core/src/platform/windows/ipc.rs 4 native_path interprocess::os::windows ipc host_mechanic crates/fbuild-core/src/platform/windows/ipc.rs 5 native_path socket2:: ipc host_mechanic crates/fbuild-core/src/platform/windows/ipc.rs 6 native_path std::os::windows::io::AsRawSocket ipc host_mechanic -crates/fbuild-core/src/platform/windows/mod.rs 12 compile_host_fact std::env::consts::ARCH host host_mechanic +crates/fbuild-core/src/platform/windows/mod.rs 13 compile_host_fact std::env::consts::ARCH host host_mechanic crates/fbuild-core/src/platform/windows/process.rs 1 native_path std::os::windows::io::AsHandle process host_mechanic crates/fbuild-core/src/platform/windows/usb_pnp.rs 13 native_path windows_sys:: process host_mechanic crates/fbuild-core/src/platform/windows/usb_pnp.rs 24 native_path windows_sys:: process host_mechanic @@ -82,32 +82,8 @@ crates/fbuild-core/src/platform/windows/usb_pnp.rs 40 native_path windows_sys:: crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs 146 attr_cfg #[cfg(windows)] host_executable host_artifact_policy crates/fbuild-daemon/src/handlers/emulator/tests_process.rs 9 attr_cfg #[cfg(windows)] host_executable host_artifact_policy crates/fbuild-daemon/src/handlers/emulator/tests_process.rs 21 attr_cfg #[cfg(not(windows))] host_executable host_artifact_policy -crates/fbuild-deploy/src/lpc.rs 27 attr_cfg #[cfg(target_os=)] device host_mechanic -crates/fbuild-deploy/src/lpc.rs 31 attr_cfg #[cfg(not(target_os=))] device host_mechanic -crates/fbuild-deploy/src/lpc.rs 972 attr_cfg #[cfg(windows)] device host_mechanic -crates/fbuild-deploy/src/lpc.rs 987 attr_cfg #[cfg(not(windows))] device host_mechanic -crates/fbuild-deploy/src/lpc_debugger_reflash.rs 294 attr_cfg #[cfg(target_os=)] device host_mechanic -crates/fbuild-deploy/src/probe_rs.rs 508 attr_cfg #[cfg(windows)] device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs 3 attr_cfg #[cfg(any(target_os=,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs 6 attr_cfg #[cfg(any(target_os=,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs 23 attr_cfg #[cfg(target_os=)] device host_mechanic -crates/fbuild-deploy/src/rp2040_mount.rs 53 attr_cfg #[cfg(not(target_os=))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 6 attr_cfg #[cfg(windows)] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 11 attr_cfg #[cfg(not(windows))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 20 attr_cfg #[cfg(any(windows,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 31 attr_cfg #[cfg(any(windows,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 71 attr_cfg #[cfg(any(windows,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 81 attr_cfg #[cfg(any(windows,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 88 attr_cfg #[cfg(any(windows,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 99 attr_cfg #[cfg(any(windows,test))] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 119 attr_cfg #[cfg(windows)] device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 123 native_path std::os::windows::ffi::OsStrExt device host_mechanic -crates/fbuild-deploy/src/rp2040_topology.rs 506 attr_cfg #[cfg(not(windows))] device host_mechanic crates/fbuild-library/src/library/library_spec.rs 266 attr_cfg #[cfg(windows)] host host_mechanic crates/fbuild-paths/src/dev_daemon_namespace.rs 83 native_path std::env::current_exe host_executable host_mechanic -crates/fbuild-serial/src/ports.rs 222 attr_cfg #[cfg(target_os=)] device host_mechanic -crates/fbuild-serial/src/ports.rs 234 attr_cfg #[cfg(target_os=)] device host_mechanic -crates/fbuild-serial/src/ports.rs 291 attr_cfg #[cfg(target_os=)] device host_mechanic crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 220 attr_cfg #[cfg(not(target_os=))] host_executable host_mechanic crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 226 attr_cfg #[cfg(target_os=)] host_executable host_mechanic crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 576 attr_cfg #[cfg(windows)] host_executable host_mechanic diff --git a/ci/test_enforce_platform_boundary.py b/ci/test_enforce_platform_boundary.py index 8f5c3e0b..bffaa95e 100644 --- a/ci/test_enforce_platform_boundary.py +++ b/ci/test_enforce_platform_boundary.py @@ -14,7 +14,10 @@ def setUpClass(cls) -> None: cls.observed = boundary.rows_from_findings(boundary.research.inventory()) def test_committed_exact_occurrence_ledger_matches_whole_tree(self) -> None: - self.assertEqual(len(self.expected), 57) + # Phase 8a (FastLED/fbuild#1314): the 24 `device`-namespace rows + # migrated behind the platform facade are gone; what remains is + # fs/ipc/host/process/host_executable work for later phases. + self.assertEqual(len(self.expected), 33) self.assertFalse(boundary.validate_ledger(self.expected)) self.assertFalse(boundary.compare(self.expected, self.observed)) diff --git a/crates/fbuild-core/src/platform/device.rs b/crates/fbuild-core/src/platform/device.rs index 70e99f0a..ed540f84 100644 --- a/crates/fbuild-core/src/platform/device.rs +++ b/crates/fbuild-core/src/platform/device.rs @@ -82,6 +82,10 @@ pub struct SerialPortFacts { /// Physical USB location paths. Empty when unavailable. These are /// identity history only and never make a phantom endpoint selectable. pub location_paths: Vec, + /// The host's short human location string for the devnode (Windows + /// `SPDRP_LOCATION_INFORMATION`, e.g. `Port_#0003.Hub_#0004`). + /// `None` where the host has no equivalent. + pub location_information: Option, } /// Map a portable `serialport` enumeration entry onto neutral facts. @@ -115,6 +119,7 @@ pub fn facts_from_port_info(info: serialport::SerialPortInfo) -> SerialPortFacts parent_instance_id: None, ancestor_instance_ids: Vec::new(), location_paths: Vec::new(), + location_information: None, } } @@ -256,6 +261,15 @@ pub fn is_picotool_reset_compatible_id(value: &str) -> bool { value.eq_ignore_ascii_case("USB\\Class_ff&SubClass_00&Prot_01") } +/// Best-effort mount of USB mass-storage block-device paths discovered by +/// the caller (Linux: `udisksctl mount --block-device`, one attempt per +/// path, 5 s timeout each; diagnostics logged, failures never fatal). A +/// no-op on hosts without a supported auto-mount mechanic — callers keep +/// their own scan/policy and derive behavior from their own enumeration. +pub fn mount_block_devices(device_paths: &[&str]) { + super::selected::device::mount_block_devices(device_paths); +} + /// Number of bounded post-operation observations the host backend wants. /// Fakes stay instant; real backends wait between observations for /// re-enumeration to settle. diff --git a/crates/fbuild-core/src/platform/host.rs b/crates/fbuild-core/src/platform/host.rs index aa47696a..6ead04a5 100644 --- a/crates/fbuild-core/src/platform/host.rs +++ b/crates/fbuild-core/src/platform/host.rs @@ -1,4 +1,7 @@ //! Neutral host identity and runtime facts. +//! +//! Host-filesystem mechanics (e.g. the home directory) live in the +//! per-OS `selected::host` tree; this module exposes the neutral API. /// Operating systems supported by the fbuild executable. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -124,6 +127,17 @@ pub fn path_list_separator_str() -> &'static str { current().path_list_separator_str() } +/// The current user's home directory: `%USERPROFILE%` on Windows (with a +/// `%HOME%` fallback for POSIX-style shells), `$HOME` elsewhere. +/// +/// Callers keep their own policy for what to do when the variable is +/// unset — the facade only reports the fact. Returned as a +/// [`NormalizedPath`](crate::path::NormalizedPath), matching the other +/// neutral facades that hand out paths. +pub fn home_dir() -> Option { + super::selected::host::home_dir() +} + #[cfg(test)] mod tests { use super::{HostArch, HostOs, HostPlatform}; diff --git a/crates/fbuild-core/src/platform/linux/device.rs b/crates/fbuild-core/src/platform/linux/device.rs index 00cd9b19..86a7be0b 100644 --- a/crates/fbuild-core/src/platform/linux/device.rs +++ b/crates/fbuild-core/src/platform/linux/device.rs @@ -33,6 +33,32 @@ pub(crate) fn live_sysfs_usb_root() -> Option { Some(NormalizedPath::from(SYSFS_USB_ROOT)) } +pub(crate) fn mount_block_devices(device_paths: &[&str]) { + for device in device_paths { + let args = ["udisksctl", "mount", "--block-device", device]; + match crate::subprocess::run_command_blocking( + &args, + None, + None, + Some(std::time::Duration::from_secs(5)), + ) { + Ok(output) if output.success() => { + tracing::debug!(device, "mounted RP-series ROM volume with udisksctl"); + } + Ok(output) => { + tracing::debug!( + device, + stderr = output.stderr.trim(), + "udisksctl could not mount RP-series ROM volume" + ); + } + Err(error) => { + tracing::debug!(device, error = %error, "RP-series ROM auto-mount unavailable"); + } + } + } +} + /// Linux implementation, factored on `sysfs_root` so unit tests can /// point at a temp dir holding a fake sysfs. pub(crate) fn detect_with_sysfs_root( diff --git a/crates/fbuild-core/src/platform/linux/host.rs b/crates/fbuild-core/src/platform/linux/host.rs new file mode 100644 index 00000000..7a8a3673 --- /dev/null +++ b/crates/fbuild-core/src/platform/linux/host.rs @@ -0,0 +1,8 @@ +//! Selected Linux host mechanics behind [`crate::platform::host`]. + +use crate::path::NormalizedPath; + +/// `$HOME`. +pub(crate) fn home_dir() -> Option { + std::env::var_os("HOME").map(NormalizedPath::new) +} diff --git a/crates/fbuild-core/src/platform/linux/mod.rs b/crates/fbuild-core/src/platform/linux/mod.rs index 75c40067..4c8bef39 100644 --- a/crates/fbuild-core/src/platform/linux/mod.rs +++ b/crates/fbuild-core/src/platform/linux/mod.rs @@ -2,6 +2,7 @@ use super::host::{HostArch, HostOs}; pub(super) mod device; pub(super) mod fs; +pub(super) mod host; pub(super) mod ipc; pub(super) mod process; pub(super) mod usb_pnp; diff --git a/crates/fbuild-core/src/platform/macos/device.rs b/crates/fbuild-core/src/platform/macos/device.rs index 9cabdcb0..10ab1d7a 100644 --- a/crates/fbuild-core/src/platform/macos/device.rs +++ b/crates/fbuild-core/src/platform/macos/device.rs @@ -28,6 +28,11 @@ pub(crate) fn live_sysfs_usb_root() -> Option { None } +pub(crate) fn mount_block_devices(_device_paths: &[&str]) { + // No fbuild-supported auto-mount mechanic on macOS: macOS auto-mounts + // USB mass-storage volumes itself. +} + /// Classify a macOS serial devnode from its device-node name. /// /// Any name we don't recognize returns `None` so the caller falls back diff --git a/crates/fbuild-core/src/platform/macos/host.rs b/crates/fbuild-core/src/platform/macos/host.rs new file mode 100644 index 00000000..75005b40 --- /dev/null +++ b/crates/fbuild-core/src/platform/macos/host.rs @@ -0,0 +1,8 @@ +//! Selected macOS host mechanics behind [`crate::platform::host`]. + +use crate::path::NormalizedPath; + +/// `$HOME`. +pub(crate) fn home_dir() -> Option { + std::env::var_os("HOME").map(NormalizedPath::new) +} diff --git a/crates/fbuild-core/src/platform/macos/mod.rs b/crates/fbuild-core/src/platform/macos/mod.rs index ba075ee8..c86cf5e6 100644 --- a/crates/fbuild-core/src/platform/macos/mod.rs +++ b/crates/fbuild-core/src/platform/macos/mod.rs @@ -2,6 +2,7 @@ use super::host::{HostArch, HostOs}; pub(super) mod device; pub(super) mod fs; +pub(super) mod host; pub(super) mod ipc; pub(super) mod process; pub(super) mod usb_pnp; diff --git a/crates/fbuild-core/src/platform/windows/device.rs b/crates/fbuild-core/src/platform/windows/device.rs index 22049cd1..cc453302 100644 --- a/crates/fbuild-core/src/platform/windows/device.rs +++ b/crates/fbuild-core/src/platform/windows/device.rs @@ -18,7 +18,8 @@ use std::ptr; use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{ CM_Get_DevNode_Status, CM_Get_Device_IDW, CM_Get_Parent, CR_NO_SUCH_DEVINST, CR_SUCCESS, DICS_FLAG_GLOBAL, DIREG_DEV, HDEVINFO, MAX_DEVICE_ID_LEN, SP_DEVINFO_DATA, - SPDRP_FRIENDLYNAME, SPDRP_HARDWAREID, SPDRP_MFG, SetupDiClassGuidsFromNameW, + SPDRP_FRIENDLYNAME, SPDRP_HARDWAREID, SPDRP_LOCATION_INFORMATION, SPDRP_MFG, + SetupDiClassGuidsFromNameW, SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, SetupDiGetClassDevsW, SetupDiGetDeviceInstanceIdW, SetupDiGetDevicePropertyW, SetupDiGetDeviceRegistryPropertyW, SetupDiOpenDevRegKey, @@ -60,6 +61,7 @@ pub(crate) fn available_serial_ports() -> io::Result> { let parent_instance_id = port_device.parent_instance_id(); let ancestor_instance_ids = ancestor_ids(port_device.devinfo_data.DevInst); let observation = port_device.pnp_observation(); + let location_information = port_device.property(SPDRP_LOCATION_INFORMATION); let port_type = port_device.port_type(instance_id.as_deref(), parent_instance_id.as_deref()); let is_usb = matches!(port_type, SerialPortTypeFacts::Usb(_)); @@ -88,6 +90,7 @@ pub(crate) fn available_serial_ports() -> io::Result> { port_device.hdi, &port_device.devinfo_data, ), + location_information, }); } } @@ -103,6 +106,7 @@ pub(crate) fn available_serial_ports() -> io::Result> { parent_instance_id: None, ancestor_instance_ids: Vec::new(), location_paths: Vec::new(), + location_information: None, }); } } @@ -120,6 +124,11 @@ pub(crate) fn live_sysfs_usb_root() -> Option { None } +pub(crate) fn mount_block_devices(_device_paths: &[&str]) { + // No fbuild-supported auto-mount mechanic on Windows: the RP-series + // ROM volume auto-assigns a drive letter without help. +} + pub(super) fn as_utf16(utf8: &str) -> Vec { utf8.encode_utf16().chain(Some(0)).collect() } diff --git a/crates/fbuild-core/src/platform/windows/host.rs b/crates/fbuild-core/src/platform/windows/host.rs new file mode 100644 index 00000000..a76af868 --- /dev/null +++ b/crates/fbuild-core/src/platform/windows/host.rs @@ -0,0 +1,11 @@ +//! Selected Windows host mechanics behind [`crate::platform::host`]. + +use crate::path::NormalizedPath; + +/// `%USERPROFILE%`, falling back to `%HOME%` for environments (MSYS, +/// cross-toolchain shells) that export only the POSIX variable. +pub(crate) fn home_dir() -> Option { + std::env::var_os("USERPROFILE") + .or_else(|| std::env::var_os("HOME")) + .map(NormalizedPath::new) +} diff --git a/crates/fbuild-core/src/platform/windows/mod.rs b/crates/fbuild-core/src/platform/windows/mod.rs index 1278ebaf..bbd714bc 100644 --- a/crates/fbuild-core/src/platform/windows/mod.rs +++ b/crates/fbuild-core/src/platform/windows/mod.rs @@ -2,6 +2,7 @@ use super::host::{HostArch, HostOs}; pub(super) mod device; pub(super) mod fs; +pub(super) mod host; pub(super) mod ipc; pub(super) mod process; pub(super) mod usb_pnp; diff --git a/crates/fbuild-deploy/src/lpc.rs b/crates/fbuild-deploy/src/lpc.rs index 8f45ceb9..0843a9e9 100644 --- a/crates/fbuild-deploy/src/lpc.rs +++ b/crates/fbuild-deploy/src/lpc.rs @@ -20,18 +20,11 @@ use crate::{DeployOutcome, Deployer, DeploymentResult}; /// lpc21isp itself. pub const LPC21ISP_PATH_ENV_VAR: &str = "FBUILD_LPC21ISP_PATH"; -/// Resolve `$HOME` (`$USERPROFILE` on Windows). Kept as a small local -/// helper so this crate does not gain a `dirs` / `home` dependency for -/// one call site — mirrors the same pattern in `fbuild-paths`. +/// Resolve the user's home directory through the neutral host facade +/// (`%USERPROFILE%` with a `%HOME%` fallback on Windows, `$HOME` +/// elsewhere) so this module carries no per-OS env logic. fn home_dir() -> Option { - #[cfg(target_os = "windows")] - { - std::env::var_os("USERPROFILE").map(PathBuf::from) - } - #[cfg(not(target_os = "windows"))] - { - std::env::var_os("HOME").map(PathBuf::from) - } + fbuild_core::platform::host::home_dir().map(|home| home.into_path_buf()) } /// The one canonical location fbuild manages lpc21isp at. FastLED/fbuild#921 @@ -969,27 +962,24 @@ mod tests { } #[test] - #[cfg(windows)] - fn normalize_port_prefixes_com10_and_above_on_windows() { - assert_eq!(normalize_lpc21isp_port("COM1"), "COM1"); - assert_eq!(normalize_lpc21isp_port("COM9"), "COM9"); - assert_eq!(normalize_lpc21isp_port("COM10"), r"\\.\COM10"); - assert_eq!(normalize_lpc21isp_port("COM99"), r"\\.\COM99"); - assert_eq!(normalize_lpc21isp_port("com10"), r"\\.\com10"); - // Already prefixed — leave alone. - assert_eq!(normalize_lpc21isp_port(r"\\.\COM10"), r"\\.\COM10"); - // Non-COM devices — leave alone. - assert_eq!(normalize_lpc21isp_port("/dev/ttyUSB0"), "/dev/ttyUSB0"); - assert_eq!(normalize_lpc21isp_port("COMx"), "COMx"); - } - - #[test] - #[cfg(not(windows))] - fn normalize_port_is_a_noop_on_non_windows() { - // On POSIX the caller sees /dev/ttyUSBn / /dev/tty.usbserial-* - // and lpc21isp opens them via plain open(2); no prefix needed. - assert_eq!(normalize_lpc21isp_port("COM10"), "COM10"); - assert_eq!(normalize_lpc21isp_port("/dev/ttyUSB0"), "/dev/ttyUSB0"); + fn normalize_port_branches_on_the_host_os_facade() { + if fbuild_core::platform::host::is_windows() { + assert_eq!(normalize_lpc21isp_port("COM1"), "COM1"); + assert_eq!(normalize_lpc21isp_port("COM9"), "COM9"); + assert_eq!(normalize_lpc21isp_port("COM10"), r"\\.\COM10"); + assert_eq!(normalize_lpc21isp_port("COM99"), r"\\.\COM99"); + assert_eq!(normalize_lpc21isp_port("com10"), r"\\.\com10"); + // Already prefixed — leave alone. + assert_eq!(normalize_lpc21isp_port(r"\\.\COM10"), r"\\.\COM10"); + // Non-COM devices — leave alone. + assert_eq!(normalize_lpc21isp_port("/dev/ttyUSB0"), "/dev/ttyUSB0"); + assert_eq!(normalize_lpc21isp_port("COMx"), "COMx"); + } else { + // On POSIX the caller sees /dev/ttyUSBn / /dev/tty.usbserial-* + // and lpc21isp opens them via plain open(2); no prefix needed. + assert_eq!(normalize_lpc21isp_port("COM10"), "COM10"); + assert_eq!(normalize_lpc21isp_port("/dev/ttyUSB0"), "/dev/ttyUSB0"); + } } #[test] diff --git a/crates/fbuild-deploy/src/lpc_debugger_reflash.rs b/crates/fbuild-deploy/src/lpc_debugger_reflash.rs index 49fac442..bbf2b674 100644 --- a/crates/fbuild-deploy/src/lpc_debugger_reflash.rs +++ b/crates/fbuild-deploy/src/lpc_debugger_reflash.rs @@ -75,7 +75,7 @@ pub const LPC_LINK2_FIRMWARE_ENV_VAR: &str = "FBUILD_LPC_LINK2_FIRMWARE"; /// tools under. Honors `FBUILD_DEV_MODE=1` for `~/.fbuild/dev/…` /// isolation, same as `find_lpc21isp` and the rest of `fbuild-paths`. pub fn managed_tools_dir() -> Option { - let home = home_dir()?; + let home = fbuild_core::platform::host::home_dir()?; let mode = if std::env::var_os("FBUILD_DEV_MODE").is_some() { "dev" } else { @@ -287,19 +287,6 @@ pub fn require_installed() -> Result<(NormalizedPath, NormalizedPath)> { Ok((dfu, fw)) } -/// Resolve `$HOME` / `%USERPROFILE%`. Kept local so this module does -/// not gain a `dirs` dependency for one call site — mirrors the same -/// helper in `fbuild_deploy::lpc`. -fn home_dir() -> Option { - #[cfg(target_os = "windows")] - { - if let Some(value) = std::env::var_os("USERPROFILE") { - return Some(NormalizedPath::new(Path::new(&value))); - } - } - std::env::var_os("HOME").map(|value| NormalizedPath::new(Path::new(&value))) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/fbuild-deploy/src/probe_rs.rs b/crates/fbuild-deploy/src/probe_rs.rs index 253852b1..ce1cedb7 100644 --- a/crates/fbuild-deploy/src/probe_rs.rs +++ b/crates/fbuild-deploy/src/probe_rs.rs @@ -117,7 +117,7 @@ pub fn probe_rs_release_asset_for_host() -> Result { /// Honors `FBUILD_DEV_MODE=1` → `~/.fbuild/dev/tools/probe-rs/` to /// match the isolation the rest of `fbuild-paths` applies. pub fn managed_probe_rs_dir() -> Option { - let home = home_dir_local()?; + let home = fbuild_core::platform::host::home_dir()?; let mode = if std::env::var_os("FBUILD_DEV_MODE").is_some() { "dev" } else { @@ -504,16 +504,6 @@ impl ProbeRsRun { } } -fn home_dir_local() -> Option { - #[cfg(windows)] - { - if let Some(v) = std::env::var_os("USERPROFILE") { - return Some(NormalizedPath::new(Path::new(&v))); - } - } - std::env::var_os("HOME").map(|value| NormalizedPath::new(Path::new(&value))) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/fbuild-deploy/src/rp2040.rs b/crates/fbuild-deploy/src/rp2040.rs index 3645947a..44169e63 100644 --- a/crates/fbuild-deploy/src/rp2040.rs +++ b/crates/fbuild-deploy/src/rp2040.rs @@ -25,7 +25,7 @@ mod preflight; mod target; #[path = "rp2040_topology.rs"] mod topology; -use mount::try_mount_linux_rom_device; +use mount::try_mount_rom_device; use target::{ describe_unhealthy, resolve_requested_runtime_target, select_cdc_candidate, serial_selector, }; @@ -472,7 +472,7 @@ fn find_uf2_volume_until( find_uf2_volume_until_with( timeout, || select_appeared_volume(volumes_before, find_uf2_volumes(&volume_roots())), - try_mount_linux_rom_device, + try_mount_rom_device, ) } diff --git a/crates/fbuild-deploy/src/rp2040_mount.rs b/crates/fbuild-deploy/src/rp2040_mount.rs index 20c8fa01..a8fb248f 100644 --- a/crates/fbuild-deploy/src/rp2040_mount.rs +++ b/crates/fbuild-deploy/src/rp2040_mount.rs @@ -1,9 +1,13 @@ -//! Linux best-effort mounting for a stock RP-series ROM volume. +//! Best-effort mounting for a stock RP-series ROM volume. +//! +//! Candidate discovery (`/dev/disk/by-id/usb-RPI_RP2*-part1`) is pure and +//! runs on every host; the actual mount mechanic lives behind +//! [`fbuild_core::platform::device::mount_block_devices`] (udisksctl on +//! Linux, a no-op where the OS auto-mounts). Callers keep the retry/policy +//! loop around this. -#[cfg(any(target_os = "linux", test))] use std::path::{Path, PathBuf}; -#[cfg(any(target_os = "linux", test))] fn rom_block_devices(by_id: &Path) -> Vec { let Ok(entries) = std::fs::read_dir(by_id) else { return Vec::new(); @@ -20,41 +24,20 @@ fn rom_block_devices(by_id: &Path) -> Vec { devices } -#[cfg(target_os = "linux")] -pub(super) fn try_mount_linux_rom_device() -> bool { +/// Try to mount any RP-series ROM block device the host exposes. Returns +/// whether any candidate existed — `false` means "nothing to mount", not +/// "mounting failed"; individual failures are logged, never fatal. +pub(super) fn try_mount_rom_device() -> bool { let devices = rom_block_devices(Path::new("/dev/disk/by-id")); - for device in &devices { - let device = device.to_string_lossy().to_string(); - let args = ["udisksctl", "mount", "--block-device", device.as_str()]; - match fbuild_core::subprocess::run_command_blocking( - &args, - None, - None, - Some(std::time::Duration::from_secs(5)), - ) { - Ok(output) if output.success() => { - tracing::debug!(device, "mounted RP-series ROM volume with udisksctl"); - } - Ok(output) => { - tracing::debug!( - device, - stderr = output.stderr.trim(), - "udisksctl could not mount RP-series ROM volume" - ); - } - Err(error) => { - tracing::debug!(device, error = %error, "RP-series ROM auto-mount unavailable"); - } - } - } + let device_strs: Vec = devices + .iter() + .map(|device| device.to_string_lossy().into_owned()) + .collect(); + let device_refs: Vec<&str> = device_strs.iter().map(String::as_str).collect(); + fbuild_core::platform::device::mount_block_devices(&device_refs); !devices.is_empty() } -#[cfg(not(target_os = "linux"))] -pub(super) fn try_mount_linux_rom_device() -> bool { - false -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/fbuild-deploy/src/rp2040_topology.rs b/crates/fbuild-deploy/src/rp2040_topology.rs index a6b206c4..f1848150 100644 --- a/crates/fbuild-deploy/src/rp2040_topology.rs +++ b/crates/fbuild-deploy/src/rp2040_topology.rs @@ -1,23 +1,41 @@ -//! USB topology capture for RP-series deploy diagnostics (Windows only). +//! USB topology capture for RP-series deploy diagnostics. +//! +//! Built entirely on [`fbuild_core::platform::device::available_serial_ports`] +//! facts: the host seam owns the SetupAPI/CfgMgr32 walk (Windows) and simply +//! reports no ancestry elsewhere. Two deliberate deltas versus the previous +//! hand-rolled fork: ports retained in host history (phantom records, +//! FastLED/fbuild#962) now get a topology line like live ones — better +//! failure diagnostics — and a port with neither an instance ID nor any +//! ancestor chain reports `None` instead of a flat "unavailable" sentence. + +use fbuild_core::platform::device; /// One-line human-readable USB topology for a runtime COM port, or `None` -/// when the platform or host cannot supply one. Never panics: every FFI -/// failure degrades to `None` rather than guessing at topology. -#[cfg(windows)] +/// when the host cannot supply one. Never panics: every missing fact +/// degrades to `None` rather than guessing at topology. pub(super) fn describe_port_topology(port_name: &str) -> Option { - windows_impl::describe_port_topology(port_name) -} - -#[cfg(not(windows))] -pub(super) fn describe_port_topology(_port_name: &str) -> Option { - None + let facts = device::available_serial_ports().ok()?; + let port = facts + .iter() + .find(|port| port.port_name.eq_ignore_ascii_case(port_name))?; + if port.instance_id.is_none() && port.ancestor_instance_ids.is_empty() { + // The host exposes no USB identity at all for this endpoint; + // report nothing rather than a flat "unavailable" sentence. + return None; + } + // An unreadable own ID degrades to no composite-ancestor skipping, + // not to a lost topology line. + let depth = classify_ancestor_chain( + port.instance_id.as_deref().unwrap_or_default(), + &port.ancestor_instance_ids, + ); + Some(format_topology(depth, port.location_information.as_deref())) } /// Coarse USB hub-tier classification derived from an ancestor /// instance-ID chain (child -> root order; the device's own ID is not -/// included). Kept separate from the FFI walk so it is deterministically -/// testable on every host OS. -#[cfg(any(windows, test))] +/// included). Pure string logic, deterministically testable on every +/// host OS. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) enum HubDepth { DirectRootPort, @@ -28,7 +46,6 @@ pub(super) enum HubDepth { Unavailable, } -#[cfg(any(windows, test))] pub(super) fn classify_ancestor_chain(own_id: &str, ancestor_ids: &[String]) -> HubDepth { // A composite-device CDC function enumerates as an interface node // (`USB\VID_xxxx&PID_yyyy&MI_00\...`) whose leading ancestor is the USB @@ -68,7 +85,6 @@ pub(super) fn classify_ancestor_chain(own_id: &str, ancestor_ids: &[String]) -> /// Extract the uppercase `VID_xxxx&PID_yyyy` token from a USB instance ID, /// dropping any interface suffix (`&MI_nn`). `None` for non-USB IDs. -#[cfg(any(windows, test))] fn vid_pid_token(instance_id: &str) -> Option { let upper = instance_id.to_ascii_uppercase(); let device_part = upper.strip_prefix("USB\\")?.split('\\').next()?; @@ -78,14 +94,12 @@ fn vid_pid_token(instance_id: &str) -> Option { Some(format!("{vid}&{pid}")) } -#[cfg(any(windows, test))] fn is_root_hub_id(instance_id: &str) -> bool { instance_id .to_ascii_uppercase() .starts_with("USB\\ROOT_HUB") } -#[cfg(any(windows, test))] fn is_usb_device_id(instance_id: &str) -> bool { let upper = instance_id.to_ascii_uppercase(); upper.starts_with("USB\\VID_") && upper.contains("&PID_") @@ -96,7 +110,6 @@ fn is_usb_device_id(instance_id: &str) -> bool { /// one-line summary appended to deploy failure messages. Facts fbuild /// cannot query (hub power mode, sibling count) are labeled explicitly /// rather than omitted or guessed. -#[cfg(any(windows, test))] pub(super) fn format_topology(depth: HubDepth, location: Option<&str>) -> String { match depth { HubDepth::DirectRootPort => match location { @@ -116,262 +129,6 @@ pub(super) fn format_topology(depth: HubDepth, location: Option<&str>) -> String } } -#[cfg(windows)] -mod windows_impl { - use super::{classify_ancestor_chain, format_topology}; - use std::ffi::{OsStr, c_void}; - use std::os::windows::ffi::OsStrExt; - - const DIGCF_PRESENT: u32 = 0x0000_0002; - const DICS_FLAG_GLOBAL: u32 = 0x0000_0001; - const DIREG_DEV: u32 = 0x0000_0001; - const KEY_READ: u32 = 0x0002_0019; - const REG_SZ: u32 = 1; - const ERROR_SUCCESS: i32 = 0; - const SPDRP_LOCATION_INFORMATION: u32 = 0x0000_000D; - const CR_SUCCESS: u32 = 0; - // Real USB hub trees are at most a handful of tiers deep; this bounds a - // wedged/cyclic CM_Get_Parent walk instead of looping forever. - const MAX_ANCESTOR_DEPTH: usize = 16; - const DEVICE_ID_BUFFER_LEN: usize = 256; - - #[repr(C)] - struct Guid { - data1: u32, - data2: u16, - data3: u16, - data4: [u8; 8], - } - - // {4D36E978-E325-11CE-BFC1-08002BE10318} -- GUID_DEVCLASS_PORTS. - const GUID_DEVCLASS_PORTS: Guid = Guid { - data1: 0x4D36_E978, - data2: 0xE325, - data3: 0x11CE, - data4: [0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18], - }; - - #[repr(C)] - struct SpDevinfoData { - cb_size: u32, - class_guid: Guid, - dev_inst: u32, - reserved: usize, - } - - #[link(name = "setupapi")] - extern "system" { - fn SetupDiGetClassDevsW( - class_guid: *const Guid, - enumerator: *const u16, - hwnd_parent: *mut c_void, - flags: u32, - ) -> *mut c_void; - fn SetupDiEnumDeviceInfo( - device_info_set: *mut c_void, - member_index: u32, - device_info_data: *mut SpDevinfoData, - ) -> i32; - fn SetupDiOpenDevRegKey( - device_info_set: *mut c_void, - device_info_data: *mut SpDevinfoData, - scope: u32, - hw_profile: u32, - key_type: u32, - sam_desired: u32, - ) -> *mut c_void; - fn SetupDiGetDeviceRegistryPropertyW( - device_info_set: *mut c_void, - device_info_data: *mut SpDevinfoData, - property: u32, - property_reg_data_type: *mut u32, - property_buffer: *mut u8, - property_buffer_size: u32, - required_size: *mut u32, - ) -> i32; - fn SetupDiDestroyDeviceInfoList(device_info_set: *mut c_void) -> i32; - } - - #[link(name = "advapi32")] - extern "system" { - fn RegQueryValueExW( - hkey: *mut c_void, - value_name: *const u16, - reserved: *mut u32, - data_type: *mut u32, - data: *mut u8, - data_size: *mut u32, - ) -> i32; - fn RegCloseKey(hkey: *mut c_void) -> i32; - } - - #[link(name = "cfgmgr32")] - extern "system" { - fn CM_Get_Parent(parent_devinst: *mut u32, devinst: u32, flags: u32) -> u32; - fn CM_Get_Device_IDW(devinst: u32, buffer: *mut u16, buffer_len: u32, flags: u32) -> u32; - } - - fn to_wide(value: &str) -> Vec { - OsStr::new(value) - .encode_wide() - .chain(std::iter::once(0)) - .collect() - } - - fn from_wide_lossy(buffer: &[u16]) -> String { - let len = buffer - .iter() - .position(|&unit| unit == 0) - .unwrap_or(buffer.len()); - String::from_utf16_lossy(&buffer[..len]) - } - - /// `SetupDiGetClassDevsW`/`SetupDiOpenDevRegKey` report failure as - /// `INVALID_HANDLE_VALUE` (all bits set), not always a null pointer. - fn is_invalid_handle(handle: *mut c_void) -> bool { - handle.is_null() || handle as usize == usize::MAX - } - - fn new_devinfo_data() -> SpDevinfoData { - SpDevinfoData { - cb_size: std::mem::size_of::() as u32, - class_guid: Guid { - data1: 0, - data2: 0, - data3: 0, - data4: [0; 8], - }, - dev_inst: 0, - reserved: 0, - } - } - - fn read_port_name(hdevinfo: *mut c_void, info: &mut SpDevinfoData) -> Option { - let hkey = unsafe { - SetupDiOpenDevRegKey(hdevinfo, info, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) - }; - if is_invalid_handle(hkey) { - return None; - } - let value_name = to_wide("PortName"); - let mut buffer = [0u8; DEVICE_ID_BUFFER_LEN]; - let mut size = buffer.len() as u32; - let mut data_type = 0u32; - let status = unsafe { - RegQueryValueExW( - hkey, - value_name.as_ptr(), - std::ptr::null_mut(), - &mut data_type, - buffer.as_mut_ptr(), - &mut size, - ) - }; - unsafe { - RegCloseKey(hkey); - } - if status != ERROR_SUCCESS || data_type != REG_SZ { - return None; - } - let word_count = ((size as usize) / 2).min(buffer.len() / 2); - let wide: Vec = buffer[..word_count * 2] - .chunks_exact(2) - .map(|pair| u16::from_ne_bytes([pair[0], pair[1]])) - .collect(); - Some(from_wide_lossy(&wide)) - } - - fn read_location(hdevinfo: *mut c_void, info: &mut SpDevinfoData) -> Option { - let mut buffer = [0u16; DEVICE_ID_BUFFER_LEN]; - let ok = unsafe { - SetupDiGetDeviceRegistryPropertyW( - hdevinfo, - info, - SPDRP_LOCATION_INFORMATION, - std::ptr::null_mut(), - buffer.as_mut_ptr() as *mut u8, - (buffer.len() * 2) as u32, - std::ptr::null_mut(), - ) - }; - if ok == 0 { - return None; - } - let text = from_wide_lossy(&buffer); - (!text.is_empty()).then_some(text) - } - - fn find_devinst_for_port(port_name: &str) -> Option<(u32, Option)> { - let hdevinfo = unsafe { - SetupDiGetClassDevsW( - &GUID_DEVCLASS_PORTS, - std::ptr::null(), - std::ptr::null_mut(), - DIGCF_PRESENT, - ) - }; - if is_invalid_handle(hdevinfo) { - return None; - } - let mut index = 0u32; - let found = loop { - let mut info = new_devinfo_data(); - let ok = unsafe { SetupDiEnumDeviceInfo(hdevinfo, index, &mut info) }; - if ok == 0 { - break None; - } - index += 1; - let Some(candidate_name) = read_port_name(hdevinfo, &mut info) else { - continue; - }; - if candidate_name.eq_ignore_ascii_case(port_name) { - let location = read_location(hdevinfo, &mut info); - break Some((info.dev_inst, location)); - } - }; - unsafe { - SetupDiDestroyDeviceInfoList(hdevinfo); - } - found - } - - fn device_instance_id(devinst: u32) -> Option { - let mut buffer = [0u16; DEVICE_ID_BUFFER_LEN]; - if unsafe { CM_Get_Device_IDW(devinst, buffer.as_mut_ptr(), buffer.len() as u32, 0) } - != CR_SUCCESS - { - return None; - } - Some(from_wide_lossy(&buffer)) - } - - fn ancestor_chain(devinst: u32) -> Vec { - let mut ids = Vec::new(); - let mut current = devinst; - for _ in 0..MAX_ANCESTOR_DEPTH { - let mut parent = 0u32; - if unsafe { CM_Get_Parent(&mut parent, current, 0) } != CR_SUCCESS { - break; - } - let Some(id) = device_instance_id(parent) else { - break; - }; - ids.push(id); - current = parent; - } - ids - } - - pub(super) fn describe_port_topology(port_name: &str) -> Option { - let (devinst, location) = find_devinst_for_port(port_name)?; - // An unreadable own ID degrades to no composite-ancestor skipping, - // not to a lost topology line. - let own_id = device_instance_id(devinst).unwrap_or_default(); - let depth = classify_ancestor_chain(&own_id, &ancestor_chain(devinst)); - Some(format_topology(depth, location.as_deref())) - } -} - #[cfg(test)] mod tests { use super::*; @@ -503,9 +260,10 @@ mod tests { ); } - #[cfg(not(windows))] #[test] - fn non_windows_never_reports_topology() { - assert_eq!(describe_port_topology("COM12"), None); + fn unknown_port_reports_no_topology_on_any_host() { + // Host-independent contract: a port name the host never enumerated + // yields `None` everywhere (Windows included), never a guess. + assert_eq!(describe_port_topology("FBUILD_NO_SUCH_PORT"), None); } } diff --git a/crates/fbuild-serial/src/ports.rs b/crates/fbuild-serial/src/ports.rs index 517f36d1..eb593a16 100644 --- a/crates/fbuild-serial/src/ports.rs +++ b/crates/fbuild-serial/src/ports.rs @@ -215,24 +215,19 @@ pub fn available_ports() -> serialport::Result> { } }) .collect(); - // Only Linux mutates the list (sysfs health enrichment). macOS and other - // unix targets keep every record at its enumeration-time health. Binding - // the `mut` inside the cfg keeps non-Linux unix targets from tripping - // `-D unused-mut`. - #[cfg(target_os = "linux")] - let ports = { - let mut ports = ports; - enrich_linux_port_health(&mut ports); - ports - }; + // Sysfs health enrichment self-noops where the host has no live sysfs + // USB tree (Windows, macOS): every record keeps its enumeration-time + // health, so one unconditional call serves every host. + let mut ports = ports; + enrich_with_sysfs_health(&mut ports); Ok(ports) } /// Overwrite `PortHealth::Unknown` entries with a concrete sysfs-derived -/// signal, for ports whose name is a `/dev/ttyXXX` device. Ports that sysfs -/// has no opinion about (non-USB ttys, ambiguous state) are left untouched. -#[cfg(target_os = "linux")] -fn enrich_linux_port_health(ports: &mut [DetectedPort]) { +/// signal, for ports whose name is a `/dev/ttyXXX` device. No-op wherever +/// the host has no live sysfs USB tree; otherwise, ports that sysfs has no +/// opinion about (non-USB ttys, ambiguous state) are left untouched. +fn enrich_with_sysfs_health(ports: &mut [DetectedPort]) { let Some(root) = crate::sysfs_usb::live_root() else { return; }; @@ -274,21 +269,18 @@ pub fn reset_usb_interface_to_bootsel(interface: &UsbResetInterface) -> std::io: /// Linux has an equivalent diagnostic (`sysfs`-derived, not Windows PnP /// problem codes), but the `UsbProblemDevice` shape doesn't fit it honestly /// (no PnP instance id, no Windows setup class) — see -/// `present_usb_problem_devices_linux` instead (not an intra-doc link: that -/// item is `cfg(target_os = "linux")`, so it does not exist to resolve -/// against when these docs are built on a non-Linux host). macOS has no -/// equivalent implemented yet (IOKit work is out of scope without a macOS -/// host). +/// [`present_usb_problem_devices_linux`] instead. macOS has no equivalent +/// implemented yet (IOKit work is out of scope without a macOS host). pub fn present_usb_problem_devices() -> Vec { fbuild_core::platform::device::present_usb_problem_devices() } /// Linux sibling of [`present_usb_problem_devices`]: `sysfs`-derived USB /// devices with a concrete, observed fault (unauthorized, unconfigured, or a -/// CDC interface with no bound driver). Diagnostics only — never used to -/// drive `PortHealth` selection directly (that happens per-tty via -/// [`available_ports`]'s Linux enrichment). -#[cfg(target_os = "linux")] +/// CDC interface with no bound driver). Empty on hosts without a live sysfs +/// USB tree (Windows, macOS). Diagnostics only — never used to drive +/// `PortHealth` selection directly (that happens per-tty via +/// [`available_ports`]'s sysfs enrichment). pub fn present_usb_problem_devices_linux() -> Vec { match crate::sysfs_usb::live_root() { Some(root) => crate::sysfs_usb::linux_usb_problem_devices_from_root(&root), diff --git a/crates/fbuild-serial/src/sysfs_usb.rs b/crates/fbuild-serial/src/sysfs_usb.rs index d4c67022..a3705e72 100644 --- a/crates/fbuild-serial/src/sysfs_usb.rs +++ b/crates/fbuild-serial/src/sysfs_usb.rs @@ -44,8 +44,7 @@ //! (`fbuild port scan` / future CLI surfacing), not for the //! `PortHealth` selection path. `ports::present_usb_problem_devices()` //! stays an empty `Vec` on Linux for that reason; see -//! `ports::present_usb_problem_devices_linux` for the sibling (Linux-only -//! item, so not an intra-doc link — this module's docs build everywhere). +//! [`crate::ports::present_usb_problem_devices_linux`] for the sibling. //! //! ## macOS //! diff --git a/dylints/enforce_platform_boundary/src/baseline.txt b/dylints/enforce_platform_boundary/src/baseline.txt index 8f9780b0..224db9d3 100644 --- a/dylints/enforce_platform_boundary/src/baseline.txt +++ b/dylints/enforce_platform_boundary/src/baseline.txt @@ -20,32 +20,8 @@ crates/fbuild-config/src/bin/enrich_boards.rs attr_cfg windows 1 crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs attr_cfg windows 0 crates/fbuild-daemon/src/handlers/emulator/tests_process.rs attr_cfg windows 0 crates/fbuild-daemon/src/handlers/emulator/tests_process.rs attr_cfg windows 1 -crates/fbuild-deploy/src/lpc.rs attr_cfg target_os 0 -crates/fbuild-deploy/src/lpc.rs attr_cfg target_os 1 -crates/fbuild-deploy/src/lpc.rs attr_cfg windows 0 -crates/fbuild-deploy/src/lpc.rs attr_cfg windows 1 -crates/fbuild-deploy/src/lpc_debugger_reflash.rs attr_cfg target_os 0 -crates/fbuild-deploy/src/probe_rs.rs attr_cfg windows 0 -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg target_os 0 -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg target_os 1 -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg target_os 2 -crates/fbuild-deploy/src/rp2040_mount.rs attr_cfg target_os 3 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 0 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 1 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 2 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 3 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 4 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 5 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 6 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 7 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 8 -crates/fbuild-deploy/src/rp2040_topology.rs attr_cfg windows 9 -crates/fbuild-deploy/src/rp2040_topology.rs native_import std::os::windows 0 crates/fbuild-library/src/library/library_spec.rs attr_cfg windows 0 crates/fbuild-paths/src/dev_daemon_namespace.rs native_import std::env::current_exe 0 -crates/fbuild-serial/src/ports.rs attr_cfg target_os 0 -crates/fbuild-serial/src/ports.rs attr_cfg target_os 1 -crates/fbuild-serial/src/ports.rs attr_cfg target_os 2 crates/fbuild-toolchain/src/toolchain/esp_qemu.rs attr_cfg target_os 0 crates/fbuild-toolchain/src/toolchain/esp_qemu.rs attr_cfg target_os 1 crates/fbuild-toolchain/src/toolchain/esp_qemu.rs attr_cfg windows 0