Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion src/core/knitr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ 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;
versionMinor: number;
versionPatch: number;
home: string;
libPaths: string[];
platform?: string;
packages: KnitrRequiredPackages;
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.");
Expand All @@ -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 : ""
Expand All @@ -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(
Expand Down
61 changes: 61 additions & 0 deletions src/core/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
23 changes: 22 additions & 1 deletion src/execute/rmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
knitrCapabilitiesMessage,
knitrInstallationMessage,
rInstallationMessage,
WindowsArmX64RError,
} from "../core/knitr.ts";
import {
DependenciesOptions,
Expand Down Expand Up @@ -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<string, unknown> = {};
if (conf.jsonResult) {
(conf.jsonResult.tools as Record<string, unknown>).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();
Expand Down Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions src/resources/capabilities/knitr.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Loading