diff --git a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs index d108f669..7f35f6f7 100644 --- a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs @@ -1,7 +1,4 @@ -using System.Reflection; - -using AIStudio.Components; -using AIStudio.Tools.Metadata; +using AIStudio.Components; using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; @@ -11,9 +8,8 @@ namespace AIStudio.Dialogs; public partial class PandocDialog : MSGComponentBase { - private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); - private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; - private static readonly RID CPU_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); + // Use runtime detection instead of metadata to ensure correct RID on dev machines: + private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID(); [Parameter] public bool ShowInstallationPage { get; set; } diff --git a/app/MindWork AI Studio/Tools/Pandoc.cs b/app/MindWork AI Studio/Tools/Pandoc.cs index d14203ea..ef6b9deb 100644 --- a/app/MindWork AI Studio/Tools/Pandoc.cs +++ b/app/MindWork AI Studio/Tools/Pandoc.cs @@ -14,14 +14,17 @@ namespace AIStudio.Tools; public static partial class Pandoc { private static string TB(string fallbackEN) => PluginSystem.I18N.I.T(fallbackEN, typeof(Pandoc).Namespace, nameof(Pandoc)); - + private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; - private static readonly RID CPU_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); - + + // Use runtime detection instead of metadata to ensure correct RID on dev machines: + private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID(); + private static readonly RID METADATA_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); + private const string DOWNLOAD_URL = "https://github.com/jgm/pandoc/releases/download"; private const string LATEST_URL = "https://github.com/jgm/pandoc/releases/latest"; - + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(Pandoc)); private static readonly Version MINIMUM_REQUIRED_VERSION = new (3, 7, 0, 2); private static readonly Version FALLBACK_VERSION = new (3, 7, 0, 2); @@ -53,6 +56,19 @@ public static partial class Pandoc try { + // + // Log a warning if the runtime-detected RID differs from the metadata RID. + // This can happen on dev machines where the metadata.txt contains stale values. + // We always use the runtime-detected RID for correct behavior. + // + if (shouldLog && CPU_ARCHITECTURE != METADATA_ARCHITECTURE) + { + LOG.LogWarning( + "Runtime-detected RID '{RuntimeRID}' differs from metadata RID '{MetadataRID}'. Using runtime-detected RID. This is expected on dev machines where metadata.txt may be outdated.", + CPU_ARCHITECTURE.ToUserFriendlyName(), + METADATA_ARCHITECTURE.ToUserFriendlyName()); + } + var preparedProcess = await PreparePandocProcess().AddArgument("--version").BuildAsync(rustService); if (shouldLog) LOG.LogInformation("Checking Pandoc availability using executable: '{Executable}' (IsLocal: {IsLocal}).", preparedProcess.StartInfo.FileName, preparedProcess.IsLocal); diff --git a/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs b/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs index 3265351f..c2c404a7 100644 --- a/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs +++ b/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs @@ -13,7 +13,10 @@ public sealed class PandocProcessBuilder { private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; - private static readonly RID CPU_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); + + // Use runtime detection instead of metadata to ensure correct RID on dev machines: + private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID(); + private static readonly RID METADATA_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(PandocProcessBuilder)); // Tracks whether the first log has been written to avoid log spam on repeated calls: @@ -122,6 +125,19 @@ public sealed class PandocProcessBuilder try { + // + // Log a warning if the runtime-detected RID differs from the metadata RID. + // This can happen on dev machines where the metadata.txt contains stale values. + // We always use the runtime-detected RID for correct behavior. + // + if (shouldLog && CPU_ARCHITECTURE != METADATA_ARCHITECTURE) + { + LOGGER.LogWarning( + "Runtime-detected RID '{RuntimeRID}' differs from metadata RID '{MetadataRID}'. Using runtime-detected RID. This is expected on dev machines where metadata.txt may be outdated.", + CPU_ARCHITECTURE.ToUserFriendlyName(), + METADATA_ARCHITECTURE.ToUserFriendlyName()); + } + // // First, we try to find the pandoc executable in the data directory. // Any local installation should be preferred over the system-wide installation. diff --git a/app/SharedTools/RIDExtensions.cs b/app/SharedTools/RIDExtensions.cs index 015357f9..d340e9a2 100644 --- a/app/SharedTools/RIDExtensions.cs +++ b/app/SharedTools/RIDExtensions.cs @@ -1,7 +1,39 @@ +using System.Runtime.InteropServices; + namespace SharedTools; public static class RIDExtensions { + /// + /// Detects the current Runtime Identifier (RID) at runtime based on OS and architecture. + /// + /// + /// This method should be preferred over reading the RID from metadata, + /// as the metadata may contain stale values in development environments. + /// + /// The detected RID for the current platform. + public static RID GetCurrentRID() + { + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + var arch = RuntimeInformation.OSArchitecture; + + return (isWindows, isLinux, isMacOS, arch) switch + { + (true, _, _, Architecture.X64) => RID.WIN_X64, + (true, _, _, Architecture.Arm64) => RID.WIN_ARM64, + + (_, true, _, Architecture.X64) => RID.LINUX_X64, + (_, true, _, Architecture.Arm64) => RID.LINUX_ARM64, + + (_, _, true, Architecture.X64) => RID.OSX_X64, + (_, _, true, Architecture.Arm64) => RID.OSX_ARM64, + + _ => RID.NONE, + }; + } + public static string AsMicrosoftRid(this RID rid) => rid switch { RID.WIN_X64 => "win-x64", diff --git a/runtime/src/pandoc.rs b/runtime/src/pandoc.rs index 47ca1626..f2dc6a8f 100644 --- a/runtime/src/pandoc.rs +++ b/runtime/src/pandoc.rs @@ -1,9 +1,14 @@ use std::path::{Path, PathBuf}; use std::fs; +use std::sync::OnceLock; +use log::warn; use tokio::process::Command; use crate::environment::DATA_DIRECTORY; use crate::metadata::META_DATA; +/// Tracks whether the RID mismatch warning has been logged. +static HAS_LOGGED_RID_MISMATCH: OnceLock<()> = OnceLock::new(); + pub struct PandocExecutable { pub executable: String, pub is_local_installation: bool, @@ -156,13 +161,51 @@ impl PandocProcessBuilder { Err("Executable not found".into()) } - /// Reads the os platform to determine the used executable name. + /// Determines the executable name based on the current OS at runtime. + /// + /// This uses runtime detection instead of metadata to ensure correct behavior + /// on dev machines where the metadata may contain stale values. fn pandoc_executable_name() -> String { - let metadata = META_DATA.lock().unwrap(); - let metadata = metadata.as_ref().unwrap(); + // Log a warning (once) if the runtime OS differs from the metadata architecture. + // This can happen on dev machines where the metadata.txt contains stale values. + HAS_LOGGED_RID_MISMATCH.get_or_init(|| { + let runtime_os = std::env::consts::OS; + let runtime_arch = std::env::consts::ARCH; - match metadata.architecture.as_str() { - "win-arm64" | "win-x64" => "pandoc.exe".to_string(), + if let Ok(metadata) = META_DATA.lock() { + if let Some(metadata) = metadata.as_ref() { + let metadata_arch = &metadata.architecture; + + // Determine expected OS from metadata: + let metadata_is_windows = metadata_arch.starts_with("win-"); + let metadata_is_macos = metadata_arch.starts_with("osx-"); + let metadata_is_linux = metadata_arch.starts_with("linux-"); + + // Compare with runtime OS: + let runtime_is_windows = runtime_os == "windows"; + let runtime_is_macos = runtime_os == "macos"; + let runtime_is_linux = runtime_os == "linux"; + + let os_mismatch = (metadata_is_windows != runtime_is_windows) + || (metadata_is_macos != runtime_is_macos) + || (metadata_is_linux != runtime_is_linux); + + if os_mismatch { + warn!( + Source = "Pandoc"; + "Runtime-detected OS '{}-{}' differs from metadata architecture '{}'. Using runtime-detected OS. This is expected on dev machines where metadata.txt may be outdated.", + runtime_os, + runtime_arch, + metadata_arch + ); + } + } + } + }); + + // Use std::env::consts::OS for runtime detection instead of metadata + match std::env::consts::OS { + "windows" => "pandoc.exe".to_string(), _ => "pandoc".to_string(), } }