Skip to content

Use dev pak on Windows CI in order to get binaries on ARM - #1467

Merged
jennybc merged 6 commits into
mainfrom
windows-arm-ci-cache
Nov 6, 2026
Merged

jennybc merged 6 commits into
mainfrom
windows-arm-ci-cache

Conversation

@jennybc

@jennybc jennybc commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

The Windows ARM job took ~34 min, ~21 min of which was building every R package from source, on every run. Dev pak installs r-universe's Windows arm64 binaries instead, which takes under a minute.

So this PR just sets pak-version: devel in setup-r-dependencies for Windows CI. #1471 tracks switching back to release pak once it has the fix.

Thanks @gaborcsardi for pointing us at dev pak! The investigation is in the comment below.

On windows-11-arm, pak rebuilds every R package from source on each
run, even after a cache hit. Cache the library with actions/cache and
install only missing packages instead. Add a temporary job showing
whether pak reinstalls a package it just installed from r-universe.
@jennybc

jennybc commented Nov 6, 2026 •

Copy link
Copy Markdown
Member Author

Note

Outcome: this PR ends up as a one-line change, pak-version: devel, because dev pak installs r-universe's Windows arm64 binaries. See "Where we ended up" at the bottom. #1471 tracks switching back to release pak. The rest of this comment is the investigation, including approaches we tried along the way and then dropped.

The windows-11-arm job is slow because of R package installation

Data from 7 recent completed test.yml runs on main, feature/no-return and lsp-try-r-task (2026-11-05 and 2026-11-06). Fuzz jobs excluded.

The arm job's median is 34 minutes. Every other test job's median is 12 minutes or less. Nearly all of the gap is the "Install R Packages Required For Tests" step: 21 minutes on arm vs. under a minute everywhere else.

Every test job

Job Total, median (min) Total, range (min) Install R packages, median Install R packages, range
windows-11-arm (stable, R release) 34 34–35 21.0 min 20.0–21.4 min
Linux (stable + release, R release) 12 11–26 0.5 min 25s–4.4 min*
windows-latest x64 (stable, R release) 11 9–17 0.7 min 36–41s
Linux (nightly, R devel) 10 7–10 0.5 min 26–36s
macos-latest (nightly, R release) 9 4–10 0.6 min 21–52s
macos-15 (stable, R 4.2) 8 7–9 0.6 min 27–40s
Linux (stable, R 4.3) 7 6–24* 0.5 min 30s–4.5 min*
Linux (stable, R 4.4) 7 6–31* 0.6 min 29s–4.3 min*
Linux (stable, R 4.5) 7 5–31* 0.5 min 24s–4.9 min*
Linux (stable, R 4.2) 6 5–7 0.5 min 21–39s
Linux (msrv, R release) 6 5–7 0.4 min 23–29s

* A Linux job occasionally spikes, with Install R taking 12–21 minutes, once in each of four runs. That's a separate, intermittent problem. The arm slowdown happens on every run.

arm vs. x64 Windows, step by step (medians)

Step windows-11-arm windows-latest (x64) arm / x64
Rust Cache 1.9 min 1.1 min ~2×
Install R 3.5 min 0.3 min ~10×
Install R packages 21.0 min 0.7 min ~30×
Build 1.4 min 1.4 min 1×
Run Tests 6.1 min 7.7 min 0.8×
Whole job 34 min 11 min ~3×

The build is no slower on arm, and the tests are actually faster. The extra 20+ minutes is all package installation, and because the arm job finishes last, it sets the wall-clock time for the whole test run.

Why

On windows-11-arm, r-lib/actions/setup-r-dependencies gets an exact cache hit on its R library, and then pak::lockfile_install() rebuilds all 41 packages from source anyway (Will install 41 packages, each [bld][cmp][dl]). stringi alone takes about 11 minutes. On x64 the same step prints ✔ Installed lockfile in under a second. Example: arm job 127591879166 vs. x64 job 127591879208, same run on main.

Three things combine.

1. The arm R installation points CRAN at r-universe, and marks it as having Windows binaries

A temporary debug step (job 128082526113, step "Show where the default repos come from") printed the R installation's C:/R/etc/repositories:

Name URL default source win.binary mac.binary
CRAN https://cran.r-universe.dev TRUE TRUE TRUE FALSE
BIOC https://bioc-release.r-universe.dev TRUE TRUE TRUE FALSE

Rscript --vanilla reports these same repos, and C:/R/etc/Rprofile.site is the stock template, so the setting comes from etc/repositories itself. The R that setup-r installs here is the community Windows ARM64 build from r-devel/windows-arm64 (CRAN has no Windows ARM64 installer), which sets CRAN to r-universe. setup-r's generated .Rprofile deliberately keeps an already-configured CRAN repo, and only overrides it if setup-r's cran: input is set.

2. pak treats that repo as source-only

With pak 0.11.1, pak::repo_status() lists https://cran.r-universe.dev with platform: source, so pak builds every package from source. But base R reads the same etc/repositories and finds arm64 Windows binaries there: install.packages() downloads all 41 packages from https://cran.r-universe.dev/bin/windows/arm64/contrib/4.6/ in about 10 seconds (see below).

3. pak never reuses source packages installed from r-universe

For a CRAN-type lockfile entry with platform: "source", pkgdepends only accepts an installed package if its DESCRIPTION has Repository: CRAN (installedok_remote_cran() in R/type-cran.R). r-universe tarballs carry Repository: https://cran.r-universe.dev, so the check fails for every package on every run, and the restored cache is never used. On x64 the lockfile entries are binaries, which pak checks by the Built field instead.

Evidence from another temporary debug job on arm (pak 0.11.1). Each variant installed glue with setup-r-dependencies and then printed the installed glue's DESCRIPTION:

Field Default repos (CRAN = r-universe) setup-r cran: https://cloud.r-project.org
Repository https://cran.r-universe.dev CRAN
RemoteRepos https://cran.r-universe.dev https://cloud.r-project.org
RemotePkgPlatform source source
repotype (from pak::lib_status()) NA cran

These fields match the pkgdepends check, but we haven't reproduced the reinstall itself in isolation. The debug job also tried reinstalling from a fresh lockfile, but that lockfile pointed at the already-installed glue (installed::…) rather than a CRAN source entry, so it didn't exercise the check.

Pointing setup-r at a real CRAN mirror would probably fix the caching, but CRAN has no arm64 Windows binaries, so every cold cache would still mean a ~20 minute source build.

What we tried along the way

  1. Our own cache plus install.packages(). We cached R_LIBS_USER with actions/cache and installed only missing packages with base install.packages(). Even with no cache, that took only 11 seconds (job 128036270810), because install.packages() got r-universe's arm64 binaries, which pak 0.11.1 ignores.
  2. install.packages() alone, no cache. At that speed a cache wasn't worth having. This took 13 seconds for the package step and 14 minutes for the whole job (job 128082526113; its test failure is an unrelated flake).
  3. Dev pak. @gaborcsardi said this is fixed in dev pak. That's r-lib/pkgcache#142, which pak bundles: "pkgcache now supports the custom binary package types of R 4.6.0 and later". With pak-version: devel, "Repo status" lists r-universe with platform aarch64-w64-mingw32-windows.binary.arm64, every lockfile entry is a binary, and the plan has no [bld] tags. The package step took 55 seconds and the whole job about 16 minutes (job 128123337275). That was with a cache miss, because the lockfile changed.

During these experiments, the other CI jobs and the x64 Windows entry were temporarily switched off on this branch, so the runs only used the arm runner. That's all reverted now.

We also found, after the fact, that someone else had already reported this exact situation: r-lib/pkgdepends#486, from cmdstanr's windows-11-arm job, using the same pak-version: devel workaround (stan-dev/cmdstanr#1304). The Repository check in point 3 above is still how pak works, but it only affects source packages, and with binaries it no longer matters for us.

Where we ended up

windows-11-arm Install R packages Whole job
Release pak (median of 7) 21.0 min 34 min
install.packages(), no cache 13s 14 min
Dev pak, cold cache 55s ~16 min

install.packages() is about 40 seconds faster, but we're going with dev pak: it's a one-line change, and it keeps arm on the same setup-r-dependencies path as every other job. Most of pak's remaining time is resolving dependencies and creating the lockfile (~21s).

The only change in this PR is pak-version: devel for setup-r-dependencies in .github/workflows/test-windows.yml, for both Windows jobs. #1471 tracks switching back to release pak.

install.packages() fetches r-universe's arm64 Windows binaries in
seconds, so the library cache isn't needed. Remove it along with
the temporary pak debug job.
@jennybc jennybc changed the title Cache R library on Windows arm CI and debug pak reinstalls Install R packages as binaries on Windows arm CI Nov 6, 2026
Release pak builds every R package from source on windows-11-arm
(~20 min). Dev pak installs r-universe's arm64 binaries (~1 min).
See #1471 for details and when to switch back.
@jennybc jennybc changed the title Install R packages as binaries on Windows arm CI Use dev pak on Windows CI in order to get binaries on ARM Nov 6, 2026
@jennybc
jennybc requested a review from DavisVaughan November 6, 2026 20:46
@jennybc
jennybc marked this pull request as ready for review November 6, 2026 20:46
@jennybc
jennybc merged commit 710ad90 into main Nov 6, 2026
18 of 19 checks passed
@jennybc
jennybc deleted the windows-arm-ci-cache branch November 6, 2026 21:12
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants