diff --git a/tauri-app/crates/micyou-plugin/src/host.rs b/tauri-app/crates/micyou-plugin/src/host.rs index e2d0c6c1a..7fdec11c9 100644 --- a/tauri-app/crates/micyou-plugin/src/host.rs +++ b/tauri-app/crates/micyou-plugin/src/host.rs @@ -98,11 +98,13 @@ pub struct DeviceSnapshot { /// Target of a cross-device plugin message. #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[serde(tag = "type", rename_all = "camelCase")] pub enum MessageTarget { /// A plugin on the same host. + #[serde(rename_all = "camelCase")] Local { plugin_id: String }, /// A plugin on the connected remote device. + #[serde(rename_all = "camelCase")] Remote { plugin_id: String }, /// Broadcast to all hosts (local + remote) subscribed to the topic. Broadcast, diff --git a/tauri-app/crates/micyou-plugin/src/manifest.rs b/tauri-app/crates/micyou-plugin/src/manifest.rs index 1014e0fe3..7c3a57882 100644 --- a/tauri-app/crates/micyou-plugin/src/manifest.rs +++ b/tauri-app/crates/micyou-plugin/src/manifest.rs @@ -645,7 +645,7 @@ mod tests { #[test] fn entry_path_ignores_wasm_runtime() { let json = r#"{ - "id": "dev.micyou.wasm_test", "name": "Wasm", "version": "1.0.0", + "id": "dev.micyou.wasm-test", "name": "Wasm", "version": "1.0.0", "runtime": "wasm", "entry": "my_plugin" }"#; let manifest = PluginManifest::from_json(json).unwrap(); diff --git a/tauri-app/crates/micyou-plugin/tests/native_loader.rs b/tauri-app/crates/micyou-plugin/tests/native_loader.rs index f01726bee..f48107511 100644 --- a/tauri-app/crates/micyou-plugin/tests/native_loader.rs +++ b/tauri-app/crates/micyou-plugin/tests/native_loader.rs @@ -12,6 +12,8 @@ use micyou_plugin::plugin::{AudioFrameCtx, PluginEvent, PluginRuntime}; use micyou_plugin::{PluginLogLevel, PluginResult}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; // ── Mock host ────────────────────────────────────────────────────────────── @@ -154,13 +156,49 @@ fn fixture_dylib() -> PathBuf { Ok(dir) => PathBuf::from(dir).join("debug"), Err(_) => workspace_root().join("target").join("debug"), }; - let prefix = "libmicyou_plugin_fixture_native"; + + // Cargo replaces hyphens with underscores in the output file name. + let base_name = "micyou_plugin_fixture_native"; + + // Determine the exact file name based on the target OS. + // Windows: my_crate.dll (No 'lib' prefix) + // macOS: libmy_crate.dylib + // Linux: libmy_crate.so + let exact_file_name = if cfg!(target_os = "windows") { + format!("{}.dll", base_name) + } else if cfg!(target_os = "macos") { + format!("lib{}.dylib", base_name) + } else { + format!("lib{}.so", base_name) + }; + + // 1. Try exact match in target/debug (Fastest and most reliable) + let direct_path = target_dir.join(&exact_file_name); + if direct_path.exists() { + return direct_path; + } + + // 2. Try exact match in target/debug/deps + let deps_path = target_dir.join("deps").join(&exact_file_name); + if deps_path.exists() { + return deps_path; + } + + // 3. Fallback to broad search in case Cargo appended hashes or used slightly different naming let mut candidates: Vec = Vec::new(); for root in [target_dir.clone(), target_dir.join("deps")] { if let Ok(entries) = std::fs::read_dir(&root) { for entry in entries.flatten() { let name = entry.file_name().to_string_lossy().to_string(); - if name.starts_with(prefix) + + // Fix: Dynamically determine the valid prefix based on the OS + let valid_prefix = if cfg!(target_os = "windows") { + name.starts_with(base_name) + } else { + name.starts_with(&format!("lib{}", base_name)) + }; + + if valid_prefix && (name.ends_with(".so") || name.ends_with(".dylib") || name.ends_with(".dll")) { candidates.push(entry.path()); @@ -168,6 +206,7 @@ fn fixture_dylib() -> PathBuf { } } } + candidates.sort(); candidates.into_iter().next().unwrap_or_else(|| { panic!( @@ -201,15 +240,16 @@ fn fixture_manifest() -> PluginManifest { ..Default::default() } } +static COPY_MUTEX: Mutex<()> = Mutex::new(()); /// Build a plugin directory with the manifest + the artifact. /// Returns (temp dir, artifact path, manifest with the correct entry name). fn stage_fixture() -> (tempfile_dir::TempDir, PathBuf, PluginManifest) { + let _lock = COPY_MUTEX.lock().unwrap(); + let dir = tempfile_dir::TempDir::new("micyou-native-fixture"); let dylib = fixture_dylib(); - // Copy the dylib so the loader opens the staged copy (isolated from cargo). - // Tests must open the SAME staged file for helpers: dlopen deduplicates by - // path, so helper symbols and the plugin instance share one data segment. + let ext = dylib .extension() .unwrap_or_default() @@ -217,7 +257,19 @@ fn stage_fixture() -> (tempfile_dir::TempDir, PathBuf, PluginManifest) { .to_string(); let entry_name = format!("fixture_native.{ext}"); let staged_path = dir.path().join(&entry_name); - std::fs::copy(&dylib, &staged_path).unwrap(); + let mut attempts = 0; + loop { + match std::fs::copy(&dylib, &staged_path) { + Ok(_) => break, + Err(e) if e.raw_os_error() == Some(32) && attempts < 5 => { + // Error 32: The process cannot access the file because it is being used by another process. + attempts += 1; + thread::sleep(Duration::from_millis(100)); + } + Err(e) => panic!("Failed to copy fixture dylib after retries: {}", e), + } + } + let mut manifest = fixture_manifest(); manifest.entry = entry_name; std::fs::write(