diff --git a/src/core/knitr.ts b/src/core/knitr.ts index d525c97ea28..beb69903efd 100644 --- a/src/core/knitr.ts +++ b/src/core/knitr.ts @@ -11,6 +11,8 @@ import { rBinaryPath, resourcePath } from "./resources.ts"; import { readYamlFromString } from "./yaml.ts"; import { coerce, satisfies } from "semver/mod.ts"; import { debug } from "../deno_ral/log.ts"; +import { isWindows } from "../deno_ral/platform.ts"; +import { isWindowsArm } from "./windows.ts"; export interface KnitrCapabilities { versionMajor: number; @@ -18,6 +20,7 @@ export interface KnitrCapabilities { versionPatch: number; home: string; libPaths: string[]; + platform?: string; packages: KnitrRequiredPackages; } @@ -68,6 +71,28 @@ export async function checkRBinary() { } } +export class WindowsArmX64RError extends Error { + constructor(msg: string) { + super(msg); + } +} + +function checkWindowsArmR(platform: string | undefined): void { + if (!platform) return; + + const isX64R = platform.includes("x86_64") || platform.includes("i386"); + + if (isX64R && isWindowsArm()) { + throw new WindowsArmX64RError( + "x64 R detected on Windows ARM.\n\n" + + "x64 R runs under emulation and is not reliable for Quarto.\n" + + "Please install native ARM64 R. \n" + + "Read about R on 64-bit Windows ARM at https://blog.r-project.org/2024/04/23/r-on-64-bit-arm-windows/\n" + + "After installation, set QUARTO_R environment variable if the correct version is not correctly found.", + ); + } +} + export async function knitrCapabilities(rBin: string | undefined) { if (!rBin) return undefined; try { @@ -105,6 +130,7 @@ export async function knitrCapabilities(rBin: string | undefined) { Object.values(pkgVersRequirement["rmarkdown"]).join(" "), ) : false; + return caps; } else { debug("\n++ Problem with results of knitr capabilities check."); @@ -115,9 +141,35 @@ export async function knitrCapabilities(rBin: string | undefined) { if (result.stderr) { debug(` with stderr from R:\n${result.stderr}`); } + + // Check if this is x64 R on Windows ARM + if (result.stdout) { + try { + const yamlMatch = result.stdout.match( + /--- YAML_START ---(.*)--- YAML_END ---/s, + ); + if (yamlMatch) { + const yamlLines = yamlMatch[1]; + const caps = readYamlFromString(yamlLines) as KnitrCapabilities; + checkWindowsArmR(caps.platform); + } + } catch (e) { + // If it's our specific x64-on-ARM error, rethrow it + if (e instanceof WindowsArmX64RError) { + throw e; + } + // Otherwise YAML parse failed, continue to return undefined + debug(" Failed to parse YAML for architecture detection"); + } + } + return undefined; } - } catch { + } catch (e) { + // Rethrow x64-on-ARM errors - these have helpful messages + if (e instanceof WindowsArmX64RError) { + throw e; + } debug( `\n++ Error while running 'capabilities/knitr.R' ${ rBin ? "with " + rBin : "" @@ -136,6 +188,12 @@ export function knitrCapabilitiesMessage(caps: KnitrCapabilities, indent = "") { for (const path of caps.libPaths) { lines.push(` - ${path}`); } + + // Show platform information if available + if (caps.platform) { + lines.push(`Platform: ${caps.platform}`); + } + lines.push(`knitr: ${caps.packages.knitr || "(None)"}`); if (caps.packages.knitr && !caps.packages.knitrVersOk) { lines.push( diff --git a/src/core/windows.ts b/src/core/windows.ts index aed3d21fab8..1c0b09e63ea 100644 --- a/src/core/windows.ts +++ b/src/core/windows.ts @@ -163,3 +163,64 @@ export async function safeWindowsExec( removeIfExists(tempFile); } } + +// Detect Windows ARM hardware using IsWow64Process2 API +// Returns true if running on ARM64 hardware (even from x64 Deno under emulation) +// +// Background: Deno.build.arch reports the architecture Deno was compiled for, +// not the actual hardware architecture. When x64 Deno runs on ARM64 under +// WOW64 emulation, Deno.build.arch still reports "x86_64". +// +// Solution: Use Windows IsWow64Process2 API which returns the native machine +// architecture regardless of emulation. This is a standard Windows API function +// available since Windows 10 (kernel32.dll is always present on Windows). +// +// Reference: Validated approach from https://github.com/cderv/quarto-windows-arm +// See: https://learn.microsoft.com/en-us/windows/win32/api/wow64apiset/nf-wow64apiset-iswow64process2 +export function isWindowsArm(): boolean { + if (!isWindows) { + return false; + } + + try { + // Load kernel32.dll + const kernel32 = Deno.dlopen("kernel32.dll", { + IsWow64Process2: { + parameters: ["pointer", "pointer", "pointer"], + result: "i32", + }, + GetCurrentProcess: { + parameters: [], + result: "pointer", + }, + }); + + // Get current process handle + const hProcess = kernel32.symbols.GetCurrentProcess(); + + // Prepare output parameters - allocate buffer for USHORT (2 bytes each) + const processMachineBuffer = new Uint16Array(1); + const nativeMachineBuffer = new Uint16Array(1); + + // Call IsWow64Process2 with pointers to buffers + const result = kernel32.symbols.IsWow64Process2( + hProcess, + Deno.UnsafePointer.of(processMachineBuffer), + Deno.UnsafePointer.of(nativeMachineBuffer), + ); + + kernel32.close(); + + if (result === 0) { + // Function failed + return false; + } + + // IMAGE_FILE_MACHINE_ARM64 = 0xAA64 = 43620 + const IMAGE_FILE_MACHINE_ARM64 = 0xAA64; + return nativeMachineBuffer[0] === IMAGE_FILE_MACHINE_ARM64; + } catch { + // IsWow64Process2 not available (Windows < 10) or other error + return false; + } +} diff --git a/src/execute/rmd.ts b/src/execute/rmd.ts index 3cbfd02e2be..4b78c0605a9 100644 --- a/src/execute/rmd.ts +++ b/src/execute/rmd.ts @@ -26,6 +26,7 @@ import { knitrCapabilitiesMessage, knitrInstallationMessage, rInstallationMessage, + WindowsArmX64RError, } from "../core/knitr.ts"; import { DependenciesOptions, @@ -139,13 +140,23 @@ title: "Title" const kMessage = "Checking R installation..........."; let caps: KnitrCapabilities | undefined; let rBin: string | undefined; + let x64ArmError: WindowsArmX64RError | undefined; const json: Record = {}; if (conf.jsonResult) { (conf.jsonResult.tools as Record).knitr = json; } const knitrCb = async () => { rBin = await checkRBinary(); - caps = await knitrCapabilities(rBin); + try { + caps = await knitrCapabilities(rBin); + } catch (e) { + if (e instanceof WindowsArmX64RError) { + x64ArmError = e; + // Don't rethrow - let caps stay undefined but capture error + } else { + throw e; // Rethrow other errors + } + } }; if (conf.jsonResult) { await knitrCb(); @@ -205,6 +216,16 @@ title: "Title" checkInfoMsg(msg); json["installed"] = false; checkInfoMsg(""); + } else if (x64ArmError) { + // x64 R on Windows ARM detected - show specific error + json["installed"] = false; + checkCompleteMessage(kMessage + "(None)\n"); + const errorLines = x64ArmError.message.split("\n"); + errorLines.forEach((line) => { + checkInfoMsg(line); + }); + json["error"] = x64ArmError.message; + checkInfoMsg(""); } else if (caps === undefined) { json["installed"] = false; checkCompleteMessage(kMessage + "(None)\n"); diff --git a/src/resources/capabilities/knitr.R b/src/resources/capabilities/knitr.R index 3d82a8a910d..17b0a6fbf78 100644 --- a/src/resources/capabilities/knitr.R +++ b/src/resources/capabilities/knitr.R @@ -8,6 +8,7 @@ cat("libPaths:\n") for (lib in .libPaths()) { cat(paste0(' - ', shQuote(lib)), "\n") } +cat("platform:", R.version[['platform']], "\n") cat("packages:\n") cat(" knitr: ") if (requireNamespace("knitr", quietly = TRUE)) {