Skip to content

Commit 9f27451

Browse files
authored
First steps towards aarch64 Windows support (#297)
1 parent 89a5822 commit 9f27451

25 files changed

+631
-221
lines changed

.github/workflows/test.yaml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ on:
4040
- 'yes'
4141
- 'no'
4242
default: 'yes'
43+
inpwindowsaarch64:
44+
description: |
45+
Build Windows aarch64
46+
required: false
47+
type: choice
48+
options:
49+
- 'yes'
50+
- 'no'
51+
default: 'yes'
4352
push:
4453
branches: [ 'dev', 'main' ]
4554
pull_request:
@@ -306,3 +315,80 @@ jobs:
306315
run: |
307316
bats tests/test-windows.sh
308317
shell: bash
318+
319+
windows-aarch64-test:
320+
if: ${{ github.event.inputs.inpwindows == '' || github.event.inputs.inpwindows == 'yes' }}
321+
needs: windows
322+
runs-on: windows-11-arm
323+
name: Windows test x86_64 build on aarch64
324+
325+
steps:
326+
- name: Checkout
327+
uses: actions/checkout@v4
328+
with:
329+
fetch-depth: 10
330+
331+
- name: Download build from artifacts
332+
uses: actions/download-artifact@v4
333+
with:
334+
name: rig-windows
335+
path: .
336+
337+
- name: Install rig
338+
run: |
339+
Start-Process .\rig-*.exe -ArgumentList "/verysilent /suppressmsgboxes" -Wait -NoNewWindow
340+
341+
- name: Install bats
342+
run: |
343+
npm install -g bats
344+
345+
- name: Run tests
346+
run: |
347+
bats tests/test-windows.sh
348+
shell: bash
349+
350+
windows-aarch64:
351+
if: ${{ github.event.inputs.inpwindowsaarch64 == '' || github.event.inputs.inpwindowsaarch64 == 'yes' }}
352+
runs-on: windows-11-arm
353+
name: Windows aarch64
354+
env:
355+
RUST_BACKTRACE: 1
356+
357+
steps:
358+
359+
- name: Checkout
360+
uses: actions/checkout@v4
361+
with:
362+
fetch-depth: 10
363+
364+
- name: Install rust
365+
uses: dtolnay/rust-toolchain@stable
366+
367+
- name: Install make
368+
run: choco install make
369+
370+
- name: Install curl
371+
run: choco install curl
372+
373+
- run: |
374+
make win
375+
shell: bash
376+
377+
- uses: actions/upload-artifact@v4
378+
if: success()
379+
with:
380+
name: rig-windows-aarch64
381+
path: 'rig-*.exe'
382+
383+
- name: Install rig
384+
run: |
385+
Start-Process .\rig-*.exe -ArgumentList "/verysilent /suppressmsgboxes" -Wait -NoNewWindow
386+
387+
- name: Install bats
388+
run: |
389+
npm install -g bats
390+
391+
- name: Run tests
392+
run: |
393+
bats tests/test-windows.sh
394+
shell: bash

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ lazy_static = "1.4.0"
3434
libc = "0.2"
3535
log = "0.4"
3636
nix = { version = "0.28.0", features = ["fs", "user"] }
37+
once_cell = "1.21.3"
3738
path-clean = "1.0.1"
3839
rand = "0.8.5"
3940
regex = "1.5.4"
@@ -63,6 +64,7 @@ clap_complete = "4.5.1"
6364
lazy_static = "1.4.0"
6465
simplelog = { version = "^0.12.0", features = ["paris"] }
6566
static_vcruntime = "2.0"
67+
whoami = "1.4.1"
6668

6769
[dev-dependencies]
6870
assert_cmd = "2.0.8"

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ rig-$(VERSION).exe: target/release/rig.exe rig.iss gsudo.exe
2929

3030
gsudo.exe:
3131
mkdir -p gsudo
32-
curl -L https://github.com/gerardog/gsudo/releases/download/v2.0.9/gsudo.portable.zip -o gsudo/gsudo.zip
32+
curl -L https://github.com/gerardog/gsudo/releases/download/v2.6.1/gsudo.portable.zip -o gsudo/gsudo.zip
3333
cd gsudo && unzip -o gsudo.zip
3434
cp gsudo/x64/gsudo.exe .
3535

src/alias.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ pub fn add_alias(ver: &str, alias: &str) -> Result<(), Box<dyn Error>> {
7575

7676
info!("Adding R-{} alias to R {}", alias, ver);
7777

78-
let base = Path::new(R_ROOT);
78+
let rroot = get_r_root();
79+
let base = Path::new(&rroot);
7980
let target = base.join(ver).join("Resources/bin/R");
8081
let linkfile = Path::new("/usr/local/bin/").join("R-".to_string() + alias);
8182

@@ -123,16 +124,16 @@ pub fn add_alias(ver: &str, alias: &str) -> Result<(), Box<dyn Error>> {
123124
pub fn add_alias(ver: &str, alias: &str) -> Result<(), Box<dyn Error>> {
124125
let msg = "Adding R-".to_string() + alias + " alias";
125126
escalate(&msg)?;
126-
let base = Path::new(R_ROOT);
127-
let bin = base.join("bin");
127+
let rroot = get_r_root();
128+
let linkdir = Path::new(RIG_LINKS_DIR);
128129

129130
// should exist at this point, but make sure
130-
std::fs::create_dir_all(&bin)?;
131+
std::fs::create_dir_all(&linkdir)?;
131132

132133
let filename = "R-".to_string() + alias + ".bat";
133-
let linkfile = bin.join(&filename);
134+
let linkfile = linkdir.join(&filename);
134135

135-
let cnt = "@\"C:\\Program Files\\R\\R-".to_string() + &ver + "\\bin\\R\" %*\n";
136+
let cnt = "@\"".to_string() + &rroot + "\\R-" + &ver + "\\bin\\R\" %*\n";
136137
let op;
137138
if linkfile.exists() {
138139
op = "Updating";
@@ -157,7 +158,8 @@ pub fn add_alias(ver: &str, alias: &str) -> Result<(), Box<dyn Error>> {
157158

158159
info!("Adding R-{} alias to R {}", alias, ver);
159160

160-
let base = Path::new(R_ROOT);
161+
let rroot = get_r_root();
162+
let base = Path::new(&rroot);
161163
let target = base.join(ver).join("bin/R");
162164
let linkfile = Path::new("/usr/local/bin/").join("R-".to_string() + alias);
163165

src/args.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ use clap::{Arg, ArgMatches, Command};
99
#[cfg(target_os = "macos")]
1010
use simplelog::*;
1111

12+
#[cfg(target_os = "windows")]
13+
mod windows_arch;
14+
#[cfg(target_os = "windows")]
15+
use crate::windows_arch::*;
16+
1217
std::include!("help-common.in");
1318

1419
#[cfg(target_os = "macos")]
@@ -23,6 +28,7 @@ std::include!("help-linux.in");
2328
pub fn rig_app() -> Command {
2429
let _arch_x86_64: &'static str = "x86_64";
2530
let _arch_arm64: &'static str = "arm64";
31+
let _arch_aarch64: &'static str = "aarch64";
2632
let mut _default_arch: &'static str = "";
2733
let app_types = [
2834
"api",
@@ -61,6 +67,14 @@ pub fn rig_app() -> Command {
6167
};
6268
}
6369

70+
#[cfg(target_os = "windows")]
71+
{
72+
_default_arch = match get_native_arch() {
73+
"aarch64" => _arch_aarch64,
74+
_ => _arch_x86_64
75+
};
76+
}
77+
6478
let mut rig = Command::new("RIG -- The R Installation Manager")
6579
.version(clap::crate_version!())
6680
.about(HELP_ABOUT_REAL.as_str())
@@ -192,6 +206,19 @@ pub fn rig_app() -> Command {
192206
);
193207
}
194208

209+
#[cfg(target_os = "windows")]
210+
{
211+
cmd_add = cmd_add.arg(
212+
Arg::new("arch")
213+
.help(HELP_ARCH)
214+
.short('a')
215+
.long("arch")
216+
.required(false)
217+
.default_value(&_default_arch)
218+
.value_parser(["aarch64", "x86_64"]),
219+
);
220+
}
221+
195222
let cmd_rm = Command::new("rm")
196223
.about("Remove R versions [aliases: del, remove, delete]")
197224
.long_about(HELP_RM)

src/args/windows_arch.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use std::ffi::{c_void, OsStr};
2+
use std::os::windows::ffi::OsStrExt;
3+
use std::sync::Once;
4+
5+
/// Windows API types
6+
type BOOL = i32;
7+
type HANDLE = *mut c_void;
8+
type USHORT = u16;
9+
10+
const FALSE: BOOL = 0;
11+
const IMAGE_FILE_MACHINE_I386: USHORT = 0x014c;
12+
const IMAGE_FILE_MACHINE_AMD64: USHORT = 0x8664;
13+
const IMAGE_FILE_MACHINE_ARM64: USHORT = 0xAA64;
14+
15+
#[link(name = "kernel32")]
16+
extern "system" {
17+
fn GetCurrentProcess() -> HANDLE;
18+
fn GetModuleHandleW(lpModuleName: *const u16) -> HANDLE;
19+
fn GetProcAddress(hModule: HANDLE, lpProcName: *const u8) -> *mut c_void;
20+
fn IsWow64Process(hProcess: HANDLE, wow64Process: *mut BOOL) -> BOOL;
21+
}
22+
23+
/// Signature of IsWow64Process2
24+
type FnIsWow64Process2 = unsafe extern "system" fn(
25+
HANDLE,
26+
*mut USHORT,
27+
*mut USHORT,
28+
) -> BOOL;
29+
30+
static INIT: Once = Once::new();
31+
static mut FN_ISWOW64PROCESS2: Option<FnIsWow64Process2> = None;
32+
33+
unsafe fn init_iswow64process2() {
34+
// Load kernel32.dll
35+
let wide_name: Vec<u16> = OsStr::new("kernel32.dll")
36+
.encode_wide()
37+
.chain(std::iter::once(0))
38+
.collect();
39+
let module = GetModuleHandleW(wide_name.as_ptr());
40+
if module.is_null() {
41+
return;
42+
}
43+
44+
// Look up IsWow64Process2
45+
let proc_name = b"IsWow64Process2\0";
46+
let ptr_fn = GetProcAddress(module, proc_name.as_ptr());
47+
if !ptr_fn.is_null() {
48+
FN_ISWOW64PROCESS2 = Some(std::mem::transmute(ptr_fn));
49+
}
50+
}
51+
52+
/// Returns a string representing the **native system architecture**, e.g.
53+
/// `"x86_64"`, `"aarch64"`, `"x86"`, or `"unknown"`.
54+
pub fn get_native_arch() -> &'static str {
55+
unsafe {
56+
INIT.call_once(|| { init_iswow64process2() });
57+
58+
let hproc = GetCurrentProcess();
59+
60+
// Try IsWow64Process2 if available
61+
if let Some(f) = FN_ISWOW64PROCESS2 {
62+
let mut proc_machine: USHORT = 0;
63+
let mut native_machine: USHORT = 0;
64+
let ok = f(hproc, &mut proc_machine, &mut native_machine);
65+
if ok != FALSE {
66+
return match native_machine {
67+
IMAGE_FILE_MACHINE_AMD64 => "x86_64",
68+
IMAGE_FILE_MACHINE_ARM64 => "aarch64",
69+
IMAGE_FILE_MACHINE_I386 => "x86",
70+
_ => "unknown",
71+
};
72+
}
73+
}
74+
75+
// Fallback: use IsWow64Process
76+
let mut is_wow64: BOOL = 0;
77+
let ok = IsWow64Process(hproc, &mut is_wow64);
78+
if ok != FALSE {
79+
// We can only distinguish 32-bit vs 64-bit, not ARM64 vs x86_64
80+
if is_wow64 != 0 {
81+
return "x86_64"; // assume 64-bit host when under WOW64
82+
} else {
83+
#[cfg(target_arch = "x86_64")]
84+
{ return "x86_64"; }
85+
#[cfg(target_arch = "aarch64")]
86+
{ return "aarch64"; }
87+
#[cfg(target_arch = "x86")]
88+
{ return "x86"; }
89+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "x86")))]
90+
return "unknown";
91+
}
92+
}
93+
94+
// Final fallback if everything fails
95+
#[cfg(target_arch = "x86_64")]
96+
{ "x86_64" }
97+
#[cfg(target_arch = "aarch64")]
98+
{ "aarch64" }
99+
#[cfg(target_arch = "x86")]
100+
{ "x86" }
101+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "x86")))]
102+
{ "unknown" }
103+
}
104+
}

0 commit comments

Comments
 (0)