From ebae86f3e6f840b1e0ee3703412a828ec9ffd1c7 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 20 Aug 2025 21:37:34 +0700 Subject: [PATCH 1/5] feat: detect cpu arch in runtime --- .../plugins/tauri-plugin-hardware/src/cpu.rs | 29 ++++- .../tauri-plugin-hardware/src/tests.rs | 123 ++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs b/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs index 5b35088cd..aa3b238e6 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs @@ -24,11 +24,38 @@ impl CpuStaticInfo { CpuStaticInfo { name, core_count: System::physical_core_count().unwrap_or(0), - arch: std::env::consts::ARCH.to_string(), + arch: CpuStaticInfo::get_runtime_arch(), extensions: CpuStaticInfo::get_extensions(), } } + fn get_runtime_arch() -> String { + // Use sysinfo to get the actual CPU architecture at runtime + let mut system = System::new(); + system.refresh_cpu_all(); + + if let Some(cpu) = system.cpus().first() { + let brand = cpu.brand().to_lowercase(); + + // Detect architecture based on CPU brand/model + if brand.contains("aarch64") || brand.contains("arm64") || brand.contains("apple m") { + "aarch64".to_string() + } else if brand.contains("x86") + || brand.contains("amd64") + || brand.contains("i386") + || brand.contains("i686") + { + "x86_64".to_string() + } else { + // Fallback to compile-time architecture if we can't detect + std::env::consts::ARCH.to_string() + } + } else { + // Fallback to compile-time architecture if no CPU info available + std::env::consts::ARCH.to_string() + } + } + // TODO: see if we need to check for all CPU extensions #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn get_extensions() -> Vec { diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs index 394092543..213cbb223 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs @@ -1,4 +1,5 @@ use crate::commands::*; +use crate::types::CpuStaticInfo; use tauri::test::mock_app; #[test] @@ -14,3 +15,125 @@ fn test_system_usage() { let usage = get_system_usage(app.handle().clone()); println!("System Usage Info: {:?}", usage); } + +#[cfg(test)] +mod cpu_tests { + use super::*; + + #[test] + fn test_cpu_static_info_new() { + let cpu_info = CpuStaticInfo::new(); + + // Test that all fields are populated + assert!(!cpu_info.name.is_empty()); + assert_ne!(cpu_info.name, "unknown"); // Should have detected a CPU name + assert!(cpu_info.core_count > 0); + assert!(!cpu_info.arch.is_empty()); + + // Architecture should be one of the expected values + assert!( + cpu_info.arch == "aarch64" || + cpu_info.arch == "x86_64" || + cpu_info.arch == std::env::consts::ARCH + ); + + // Extensions should be a valid list (can be empty on non-x86) + assert!(cpu_info.extensions.is_empty() || !cpu_info.extensions.is_empty()); + + println!("CPU Info: {:?}", cpu_info); + } + + #[test] + fn test_cpu_info_consistency() { + // Test that multiple calls return consistent information + let info1 = CpuStaticInfo::new(); + let info2 = CpuStaticInfo::new(); + + assert_eq!(info1.name, info2.name); + assert_eq!(info1.core_count, info2.core_count); + assert_eq!(info1.arch, info2.arch); + assert_eq!(info1.extensions, info2.extensions); + } + + #[test] + fn test_cpu_name_not_empty() { + let cpu_info = CpuStaticInfo::new(); + assert!(!cpu_info.name.is_empty()); + assert!(cpu_info.name.len() > 0); + } + + #[test] + fn test_core_count_positive() { + let cpu_info = CpuStaticInfo::new(); + assert!(cpu_info.core_count > 0); + } + + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn test_x86_extensions() { + let cpu_info = CpuStaticInfo::new(); + + // On x86/x86_64, we should always have at least FPU + assert!(cpu_info.extensions.contains(&"fpu".to_string())); + + // Check that all extensions are valid x86 feature names + let valid_extensions = [ + "fpu", "mmx", "sse", "sse2", "sse3", "ssse3", "sse4_1", "sse4_2", + "pclmulqdq", "avx", "avx2", "avx512_f", "avx512_dq", "avx512_ifma", + "avx512_pf", "avx512_er", "avx512_cd", "avx512_bw", "avx512_vl", + "avx512_vbmi", "avx512_vbmi2", "avx512_vnni", "avx512_bitalg", + "avx512_vpopcntdq", "avx512_vp2intersect", "aes", "f16c" + ]; + + for ext in &cpu_info.extensions { + assert!( + valid_extensions.contains(&ext.as_str()), + "Unknown extension: {}", + ext + ); + } + } + + #[test] + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn test_non_x86_extensions() { + let cpu_info = CpuStaticInfo::new(); + + // On non-x86 architectures, extensions should be empty + assert!(cpu_info.extensions.is_empty()); + } + + #[test] + fn test_arch_detection() { + let cpu_info = CpuStaticInfo::new(); + + // Architecture should be a valid string + assert!(!cpu_info.arch.is_empty()); + + // Should be one of the common architectures + let common_archs = ["x86_64", "aarch64", "arm", "x86"]; + let is_common_arch = common_archs.iter().any(|&arch| cpu_info.arch == arch); + let is_compile_time_arch = cpu_info.arch == std::env::consts::ARCH; + + assert!( + is_common_arch || is_compile_time_arch, + "Unexpected architecture: {}", + cpu_info.arch + ); + } + + #[test] + fn test_cpu_info_serialization() { + let cpu_info = CpuStaticInfo::new(); + + // Test that the struct can be serialized (since it derives Serialize) + let serialized = serde_json::to_string(&cpu_info); + assert!(serialized.is_ok()); + + let json_str = serialized.unwrap(); + assert!(json_str.contains("name")); + assert!(json_str.contains("core_count")); + assert!(json_str.contains("arch")); + assert!(json_str.contains("extensions")); + } +} From 2398c0ab337f75dae1ce10d8b9f655bfbe87b663 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 20 Aug 2025 22:22:25 +0700 Subject: [PATCH 2/5] Update src-tauri/plugins/tauri-plugin-hardware/src/tests.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src-tauri/plugins/tauri-plugin-hardware/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs index 213cbb223..f05a2f759 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs @@ -38,7 +38,6 @@ mod cpu_tests { ); // Extensions should be a valid list (can be empty on non-x86) - assert!(cpu_info.extensions.is_empty() || !cpu_info.extensions.is_empty()); println!("CPU Info: {:?}", cpu_info); } From 973a8dd8ccffa300756c467126cf47a9d2f47a8b Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 21 Aug 2025 10:47:26 +0700 Subject: [PATCH 3/5] fix: simplify cpu arch detection --- .../plugins/tauri-plugin-hardware/src/cpu.rs | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs b/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs index aa3b238e6..926ca04a2 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs @@ -24,38 +24,11 @@ impl CpuStaticInfo { CpuStaticInfo { name, core_count: System::physical_core_count().unwrap_or(0), - arch: CpuStaticInfo::get_runtime_arch(), + arch: System::cpu_arch(), extensions: CpuStaticInfo::get_extensions(), } } - fn get_runtime_arch() -> String { - // Use sysinfo to get the actual CPU architecture at runtime - let mut system = System::new(); - system.refresh_cpu_all(); - - if let Some(cpu) = system.cpus().first() { - let brand = cpu.brand().to_lowercase(); - - // Detect architecture based on CPU brand/model - if brand.contains("aarch64") || brand.contains("arm64") || brand.contains("apple m") { - "aarch64".to_string() - } else if brand.contains("x86") - || brand.contains("amd64") - || brand.contains("i386") - || brand.contains("i686") - { - "x86_64".to_string() - } else { - // Fallback to compile-time architecture if we can't detect - std::env::consts::ARCH.to_string() - } - } else { - // Fallback to compile-time architecture if no CPU info available - std::env::consts::ARCH.to_string() - } - } - // TODO: see if we need to check for all CPU extensions #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn get_extensions() -> Vec { From 3a36353b020fcabf24ef4d6d62da3064f0a6575d Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 21 Aug 2025 10:54:35 +0700 Subject: [PATCH 4/5] fix: backend variant selection --- extensions/llamacpp-extension/src/backend.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/extensions/llamacpp-extension/src/backend.ts b/extensions/llamacpp-extension/src/backend.ts index a597a9a15..b47839d58 100644 --- a/extensions/llamacpp-extension/src/backend.ts +++ b/extensions/llamacpp-extension/src/backend.ts @@ -43,9 +43,9 @@ export async function listSupportedBackends(): Promise< if (features.vulkan) supportedBackends.push('win-vulkan-x64') } // not available yet, placeholder for future - else if (sysType == 'windows-aarch64') { + else if (sysType === 'windows-aarch64' || sysType === 'windows-arm64') { supportedBackends.push('win-arm64') - } else if (sysType == 'linux-x86_64') { + } else if (sysType === 'linux-x86_64' || sysType === 'linux-x86') { supportedBackends.push('linux-noavx-x64') if (features.avx) supportedBackends.push('linux-avx-x64') if (features.avx2) supportedBackends.push('linux-avx2-x64') @@ -69,11 +69,11 @@ export async function listSupportedBackends(): Promise< if (features.vulkan) supportedBackends.push('linux-vulkan-x64') } // not available yet, placeholder for future - else if (sysType === 'linux-aarch64') { + else if (sysType === 'linux-aarch64' || sysType === 'linux-arm64') { supportedBackends.push('linux-arm64') - } else if (sysType === 'macos-x86_64') { + } else if (sysType === 'macos-x86_64' || sysType === 'macos-x86') { supportedBackends.push('macos-x64') - } else if (sysType === 'macos-aarch64') { + } else if (sysType === 'macos-aarch64' || sysType === 'macos-arm64') { supportedBackends.push('macos-arm64') } @@ -262,10 +262,7 @@ async function _getSupportedFeatures() { features.cuda12 = true } // Vulkan support check - only discrete GPUs with 6GB+ VRAM - if ( - gpuInfo.vulkan_info?.api_version && - gpuInfo.total_memory >= 6 * 1024 - ) { + if (gpuInfo.vulkan_info?.api_version && gpuInfo.total_memory >= 6 * 1024) { // 6GB (total_memory is in MB) features.vulkan = true } From 51a9021994120583e1576525e5692d3cac7498e6 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 21 Aug 2025 11:09:37 +0700 Subject: [PATCH 5/5] fix: test --- src-tauri/plugins/tauri-plugin-hardware/src/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs index f05a2f759..1d4975104 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs @@ -33,6 +33,7 @@ mod cpu_tests { // Architecture should be one of the expected values assert!( cpu_info.arch == "aarch64" || + cpu_info.arch == "arm64" || cpu_info.arch == "x86_64" || cpu_info.arch == std::env::consts::ARCH ); @@ -110,7 +111,7 @@ mod cpu_tests { assert!(!cpu_info.arch.is_empty()); // Should be one of the common architectures - let common_archs = ["x86_64", "aarch64", "arm", "x86"]; + let common_archs = ["x86_64", "aarch64", "arm", "arm64", "x86"]; let is_common_arch = common_archs.iter().any(|&arch| cpu_info.arch == arch); let is_compile_time_arch = cpu_info.arch == std::env::consts::ARCH;