From eb2e7919646317cef4554f3f887c0d6c62b78206 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 6 Nov 2026 10:10:49 -0700 Subject: [PATCH 1/6] Cache R library on Windows arm CI and debug pak reinstalls 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. --- .github/workflows/test-windows.yml | 133 ++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 54215a6804..04bac270ee 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -19,6 +19,19 @@ jobs: timeout-minutes: 60 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # R packages required for tests. Shared by both install paths below. + R_TEST_PACKAGES: >- + data.table + dplyr + haven + htmltools + R6 + ragg + readr + renv + rstudioapi + S7 + tibble steps: - uses: actions/checkout@v4 @@ -34,6 +47,7 @@ jobs: tool: cargo-nextest - name: Install R + id: setup-r uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} @@ -46,21 +60,50 @@ jobs: windows-path-include-rtools: false - name: Install R Packages Required For Tests + if: runner.arch != 'ARM64' uses: r-lib/actions/setup-r-dependencies@v2 with: dependencies: '"hard"' - packages: - data.table - dplyr - haven - htmltools - R6 - ragg - readr - renv - rstudioapi - S7 - tibble + packages: ${{ env.R_TEST_PACKAGES }} + + # On windows-11-arm, `setup-r-dependencies` restores its library cache but + # then pak rebuilds every package from source anyway (~20 min, mostly + # stringi). Until that's resolved upstream, cache the library ourselves + # and only install packages that are missing from it. + # + # The key includes a hash of this file, so editing the package list saves + # a fresh cache. `restore-keys` lets that run start from the previous + # library and build only what's new. + - name: Restore R Package Library (arm64) + if: runner.arch == 'ARM64' + uses: actions/cache@v5 + with: + path: ${{ env.R_LIBS_USER }} + key: r-lib-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.setup-r.outputs.installed-r-version }}-${{ hashFiles('.github/workflows/test-windows.yml') }} + restore-keys: | + r-lib-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.setup-r.outputs.installed-r-version }}- + + - name: Install Missing R Packages Required For Tests (arm64) + if: runner.arch == 'ARM64' + shell: Rscript {0} + run: | + lib <- Sys.getenv("R_LIBS_USER") + dir.create(lib, recursive = TRUE, showWarnings = FALSE) + .libPaths(c(lib, .libPaths())) + + pkgs <- strsplit(Sys.getenv("R_TEST_PACKAGES"), "[[:space:]]+")[[1]] + missing <- setdiff(pkgs, rownames(installed.packages())) + message("Already installed: ", paste(setdiff(pkgs, missing), collapse = ", ")) + message("Installing: ", paste(missing, collapse = ", ")) + if (length(missing)) { + install.packages(missing, lib = lib) + } + + # `install.packages()` only warns on failure, so check explicitly + still_missing <- setdiff(pkgs, rownames(installed.packages())) + if (length(still_missing)) { + stop("Failed to install: ", paste(still_missing, collapse = ", ")) + } - name: Build run: | @@ -72,3 +115,69 @@ jobs: R_PROFILE_USER: ${{ github.workspace }}/.github/Rprofile run: | cargo nextest run + + # TEMPORARY: diagnose why pak reinstalls from-source packages on arm64. + # Installs `glue` via `setup-r-dependencies`, then installs from a fresh + # lockfile into the same library to see whether pak plans a reinstall. + # Compare the default repos (CRAN = cran.r-universe.dev, set by the R arm64 + # build) with an explicit CRAN mirror. Remove before merging. + windows-arm-pak-debug: + name: "pak debug, OS: windows-11-arm, cran: ${{ matrix.cran || 'default' }}" + runs-on: windows-11-arm + strategy: + fail-fast: false + matrix: + cran: ['', 'https://cloud.r-project.org'] + timeout-minutes: 30 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - name: Install R + uses: r-lib/actions/setup-r@v2 + with: + r-version: release + use-public-rspm: true + cran: ${{ matrix.cran }} + windows-path-include-rtools: false + + - name: Install glue with setup-r-dependencies + uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache: false + packages: glue + + - name: Inspect installed glue and re-run lockfile install + shell: Rscript {0} + run: | + library(pak, lib.loc = Sys.getenv("R_LIB_FOR_PAK")) + lib <- Sys.getenv("R_LIBS_USER") + + cat("::group::pak version\n") + print(packageVersion("pak", lib.loc = Sys.getenv("R_LIB_FOR_PAK"))) + cat("::endgroup::\n") + + cat("::group::getOption('repos')\n") + print(getOption("repos")) + cat("::endgroup::\n") + + cat("::group::Installed glue DESCRIPTION\n") + desc <- read.dcf(file.path(lib, "glue", "DESCRIPTION")) + fields <- c("Package", "Version", "Repository", "Built", "RemoteType", "RemoteRepos", "RemotePkgPlatform", "RemoteSha") + print(t(desc[, intersect(fields, colnames(desc)), drop = FALSE])) + cat("::endgroup::\n") + + cat("::group::pak::lib_status() for glue\n") + status <- pak::lib_status(lib) + print(as.list(status[status$package == "glue", ])) + cat("::endgroup::\n") + + cat("::group::Fresh lockfile entry for glue\n") + pak::lockfile_create("glue", lockfile = "debug.lock", lib = NULL) + writeLines(readLines("debug.lock")) + cat("::endgroup::\n") + + # The key output: "Will install 1 package" means pak doesn't accept + # the glue it just installed as satisfying the lockfile. + pak::lockfile_install("debug.lock", lib = lib) From d358dfd9b3d5675021c8c3e602d95594179d61f6 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 6 Nov 2026 11:24:15 -0700 Subject: [PATCH 2/6] Install arm64 R packages as binaries; drop cache and debug job 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. --- .github/workflows/test-windows.yml | 105 +++-------------------------- 1 file changed, 11 insertions(+), 94 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 04bac270ee..2e57457771 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -47,7 +47,6 @@ jobs: tool: cargo-nextest - name: Install R - id: setup-r uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} @@ -66,24 +65,13 @@ jobs: dependencies: '"hard"' packages: ${{ env.R_TEST_PACKAGES }} - # On windows-11-arm, `setup-r-dependencies` restores its library cache but - # then pak rebuilds every package from source anyway (~20 min, mostly - # stringi). Until that's resolved upstream, cache the library ourselves - # and only install packages that are missing from it. - # - # The key includes a hash of this file, so editing the package list saves - # a fresh cache. `restore-keys` lets that run start from the previous - # library and build only what's new. - - name: Restore R Package Library (arm64) - if: runner.arch == 'ARM64' - uses: actions/cache@v5 - with: - path: ${{ env.R_LIBS_USER }} - key: r-lib-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.setup-r.outputs.installed-r-version }}-${{ hashFiles('.github/workflows/test-windows.yml') }} - restore-keys: | - r-lib-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.setup-r.outputs.installed-r-version }}- - - - name: Install Missing R Packages Required For Tests (arm64) + # On windows-11-arm, pak treats the default CRAN repo (cran.r-universe.dev, + # set by the R arm64 build) as source-only and builds every package from + # source (~20 min, mostly stringi). It also never reuses those packages + # from the `setup-r-dependencies` cache, so it pays that cost on every run. + # `install.packages()` gets r-universe's arm64 binaries instead, which + # takes seconds, so no cache is needed. + - name: Install R Packages Required For Tests (arm64) if: runner.arch == 'ARM64' shell: Rscript {0} run: | @@ -92,17 +80,12 @@ jobs: .libPaths(c(lib, .libPaths())) pkgs <- strsplit(Sys.getenv("R_TEST_PACKAGES"), "[[:space:]]+")[[1]] - missing <- setdiff(pkgs, rownames(installed.packages())) - message("Already installed: ", paste(setdiff(pkgs, missing), collapse = ", ")) - message("Installing: ", paste(missing, collapse = ", ")) - if (length(missing)) { - install.packages(missing, lib = lib) - } + install.packages(pkgs, lib = lib) # `install.packages()` only warns on failure, so check explicitly - still_missing <- setdiff(pkgs, rownames(installed.packages())) - if (length(still_missing)) { - stop("Failed to install: ", paste(still_missing, collapse = ", ")) + missing <- setdiff(pkgs, rownames(installed.packages())) + if (length(missing)) { + stop("Failed to install: ", paste(missing, collapse = ", ")) } - name: Build @@ -115,69 +98,3 @@ jobs: R_PROFILE_USER: ${{ github.workspace }}/.github/Rprofile run: | cargo nextest run - - # TEMPORARY: diagnose why pak reinstalls from-source packages on arm64. - # Installs `glue` via `setup-r-dependencies`, then installs from a fresh - # lockfile into the same library to see whether pak plans a reinstall. - # Compare the default repos (CRAN = cran.r-universe.dev, set by the R arm64 - # build) with an explicit CRAN mirror. Remove before merging. - windows-arm-pak-debug: - name: "pak debug, OS: windows-11-arm, cran: ${{ matrix.cran || 'default' }}" - runs-on: windows-11-arm - strategy: - fail-fast: false - matrix: - cran: ['', 'https://cloud.r-project.org'] - timeout-minutes: 30 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - name: Install R - uses: r-lib/actions/setup-r@v2 - with: - r-version: release - use-public-rspm: true - cran: ${{ matrix.cran }} - windows-path-include-rtools: false - - - name: Install glue with setup-r-dependencies - uses: r-lib/actions/setup-r-dependencies@v2 - with: - cache: false - packages: glue - - - name: Inspect installed glue and re-run lockfile install - shell: Rscript {0} - run: | - library(pak, lib.loc = Sys.getenv("R_LIB_FOR_PAK")) - lib <- Sys.getenv("R_LIBS_USER") - - cat("::group::pak version\n") - print(packageVersion("pak", lib.loc = Sys.getenv("R_LIB_FOR_PAK"))) - cat("::endgroup::\n") - - cat("::group::getOption('repos')\n") - print(getOption("repos")) - cat("::endgroup::\n") - - cat("::group::Installed glue DESCRIPTION\n") - desc <- read.dcf(file.path(lib, "glue", "DESCRIPTION")) - fields <- c("Package", "Version", "Repository", "Built", "RemoteType", "RemoteRepos", "RemotePkgPlatform", "RemoteSha") - print(t(desc[, intersect(fields, colnames(desc)), drop = FALSE])) - cat("::endgroup::\n") - - cat("::group::pak::lib_status() for glue\n") - status <- pak::lib_status(lib) - print(as.list(status[status$package == "glue", ])) - cat("::endgroup::\n") - - cat("::group::Fresh lockfile entry for glue\n") - pak::lockfile_create("glue", lockfile = "debug.lock", lib = NULL) - writeLines(readLines("debug.lock")) - cat("::endgroup::\n") - - # The key output: "Will install 1 package" means pak doesn't accept - # the glue it just installed as satisfying the lockfile. - pak::lockfile_install("debug.lock", lib = lib) From 0e84f9a96d1417641c908b92bfe138e0b571565d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 6 Nov 2026 11:41:57 -0700 Subject: [PATCH 3/6] Let's just focus on windows arm for the moment --- .github/workflows/check-rust.yml | 3 ++- .github/workflows/test-windows.yml | 3 ++- .github/workflows/test.yml | 25 +++++++++++++------------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 3015ab6196..759abdae74 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -4,7 +4,8 @@ on: push: branches: - main - pull_request: + # TEMPORARY: disabled while investigating arm64 repos + # pull_request: jobs: formatting: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 2e57457771..2a36506f7d 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -13,7 +13,8 @@ jobs: matrix: config: # x64 - - { rust: 'stable', r: 'release', runs_on: 'windows-latest' } + # TEMPORARY: x64 disabled while investigating arm64 repos + # - { rust: 'stable', r: 'release', runs_on: 'windows-latest' } # arm64 - { rust: 'stable', r: 'release', runs_on: 'windows-11-arm' } timeout-minutes: 60 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index acbb5e5dc7..d2f2738d93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,22 +7,23 @@ on: workflow_dispatch: jobs: - test_macos: - name: Test macOS - uses: ./.github/workflows/test-macos.yml - secrets: inherit + # TEMPORARY: only Windows while investigating arm64 repos + # test_macos: + # name: Test macOS + # uses: ./.github/workflows/test-macos.yml + # secrets: inherit test_windows: name: Test Windows uses: ./.github/workflows/test-windows.yml secrets: inherit - test_linux: - name: Test Linux - uses: ./.github/workflows/test-linux.yml - secrets: inherit + # test_linux: + # name: Test Linux + # uses: ./.github/workflows/test-linux.yml + # secrets: inherit - test_fuzz: - name: Test Fuzz - uses: ./.github/workflows/test-fuzz.yml - secrets: inherit + # test_fuzz: + # name: Test Fuzz + # uses: ./.github/workflows/test-fuzz.yml + # secrets: inherit From eb1ac613eb50837f146be79e66c59b8f20efa9d9 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 6 Nov 2026 11:42:21 -0700 Subject: [PATCH 4/6] Who's setting `repos`? --- .github/workflows/test-windows.yml | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 2a36506f7d..d3a87839e1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -59,6 +59,40 @@ jobs: # https://github.com/briansmith/ring/blob/main/BUILDING.md windows-path-include-rtools: false + # TEMPORARY: find out where the arm64 R installation gets its + # cran.r-universe.dev repo from. `--vanilla` skips the site and user + # profiles (including the one setup-r writes), so any repo still set + # here comes from the R installation itself. + - name: Show where the default repos come from (arm64, TEMPORARY) + if: runner.arch == 'ARM64' + shell: Rscript --vanilla {0} + run: | + cat("R.version.string: ", R.version.string, "\n", sep = "") + cat("R.home(): ", R.home(), "\n", sep = "") + cat("R_REPOSITORIES env var: ", Sys.getenv("R_REPOSITORIES", ""), "\n", sep = "") + + cat("::group::getOption('repos') with --vanilla\n") + print(getOption("repos")) + cat("::endgroup::\n") + + show_file <- function(path) { + cat("::group::", path, "\n", sep = "") + if (nzchar(path) && file.exists(path)) { + writeLines(readLines(path)) + } else { + cat("\n") + } + cat("::endgroup::\n") + } + show_file(file.path(R.home("etc"), "repositories")) + show_file(file.path(R.home("etc"), "Rprofile.site")) + show_file(Sys.getenv("R_PROFILE")) + show_file(file.path(Sys.getenv("USERPROFILE"), "Documents", ".Rprofile")) + + cat("::group::Files in R.home('etc')\n") + print(list.files(R.home("etc"), recursive = TRUE)) + cat("::endgroup::\n") + - name: Install R Packages Required For Tests if: runner.arch != 'ARM64' uses: r-lib/actions/setup-r-dependencies@v2 From 60454f822a788a4905cc400301dbdb18ea252561 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 6 Nov 2026 13:02:56 -0700 Subject: [PATCH 5/6] TEMPORARY: try dev pak for the arm64 Windows job on the advice of Gabor --- .github/workflows/test-windows.yml | 86 +++++------------------------- 1 file changed, 14 insertions(+), 72 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index d3a87839e1..4580d964a3 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -20,19 +20,6 @@ jobs: timeout-minutes: 60 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # R packages required for tests. Shared by both install paths below. - R_TEST_PACKAGES: >- - data.table - dplyr - haven - htmltools - R6 - ragg - readr - renv - rstudioapi - S7 - tibble steps: - uses: actions/checkout@v4 @@ -59,69 +46,24 @@ jobs: # https://github.com/briansmith/ring/blob/main/BUILDING.md windows-path-include-rtools: false - # TEMPORARY: find out where the arm64 R installation gets its - # cran.r-universe.dev repo from. `--vanilla` skips the site and user - # profiles (including the one setup-r writes), so any repo still set - # here comes from the R installation itself. - - name: Show where the default repos come from (arm64, TEMPORARY) - if: runner.arch == 'ARM64' - shell: Rscript --vanilla {0} - run: | - cat("R.version.string: ", R.version.string, "\n", sep = "") - cat("R.home(): ", R.home(), "\n", sep = "") - cat("R_REPOSITORIES env var: ", Sys.getenv("R_REPOSITORIES", ""), "\n", sep = "") - - cat("::group::getOption('repos') with --vanilla\n") - print(getOption("repos")) - cat("::endgroup::\n") - - show_file <- function(path) { - cat("::group::", path, "\n", sep = "") - if (nzchar(path) && file.exists(path)) { - writeLines(readLines(path)) - } else { - cat("\n") - } - cat("::endgroup::\n") - } - show_file(file.path(R.home("etc"), "repositories")) - show_file(file.path(R.home("etc"), "Rprofile.site")) - show_file(Sys.getenv("R_PROFILE")) - show_file(file.path(Sys.getenv("USERPROFILE"), "Documents", ".Rprofile")) - - cat("::group::Files in R.home('etc')\n") - print(list.files(R.home("etc"), recursive = TRUE)) - cat("::endgroup::\n") - - name: Install R Packages Required For Tests - if: runner.arch != 'ARM64' uses: r-lib/actions/setup-r-dependencies@v2 with: + # TEMPORARY: does dev pak use the r-universe arm64 binaries and reuse the cache? + pak-version: devel dependencies: '"hard"' - packages: ${{ env.R_TEST_PACKAGES }} - - # On windows-11-arm, pak treats the default CRAN repo (cran.r-universe.dev, - # set by the R arm64 build) as source-only and builds every package from - # source (~20 min, mostly stringi). It also never reuses those packages - # from the `setup-r-dependencies` cache, so it pays that cost on every run. - # `install.packages()` gets r-universe's arm64 binaries instead, which - # takes seconds, so no cache is needed. - - name: Install R Packages Required For Tests (arm64) - if: runner.arch == 'ARM64' - shell: Rscript {0} - run: | - lib <- Sys.getenv("R_LIBS_USER") - dir.create(lib, recursive = TRUE, showWarnings = FALSE) - .libPaths(c(lib, .libPaths())) - - pkgs <- strsplit(Sys.getenv("R_TEST_PACKAGES"), "[[:space:]]+")[[1]] - install.packages(pkgs, lib = lib) - - # `install.packages()` only warns on failure, so check explicitly - missing <- setdiff(pkgs, rownames(installed.packages())) - if (length(missing)) { - stop("Failed to install: ", paste(missing, collapse = ", ")) - } + packages: + data.table + dplyr + haven + htmltools + R6 + ragg + readr + renv + rstudioapi + S7 + tibble - name: Build run: | From 3ad6e3e646eaf89c420c45d414e91d7576c6981d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 6 Nov 2026 13:37:47 -0700 Subject: [PATCH 6/6] Use dev pak on Windows CI 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. --- .github/workflows/check-rust.yml | 3 +-- .github/workflows/test-windows.yml | 5 ++--- .github/workflows/test.yml | 25 ++++++++++++------------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 759abdae74..3015ab6196 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -4,8 +4,7 @@ on: push: branches: - main - # TEMPORARY: disabled while investigating arm64 repos - # pull_request: + pull_request: jobs: formatting: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 4580d964a3..1f83d2092d 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -13,8 +13,7 @@ jobs: matrix: config: # x64 - # TEMPORARY: x64 disabled while investigating arm64 repos - # - { rust: 'stable', r: 'release', runs_on: 'windows-latest' } + - { rust: 'stable', r: 'release', runs_on: 'windows-latest' } # arm64 - { rust: 'stable', r: 'release', runs_on: 'windows-11-arm' } timeout-minutes: 60 @@ -49,7 +48,7 @@ jobs: - name: Install R Packages Required For Tests uses: r-lib/actions/setup-r-dependencies@v2 with: - # TEMPORARY: does dev pak use the r-universe arm64 binaries and reuse the cache? + # Revert to release pak: https://github.com/posit-dev/ark/issues/1471 pak-version: devel dependencies: '"hard"' packages: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d2f2738d93..acbb5e5dc7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,23 +7,22 @@ on: workflow_dispatch: jobs: - # TEMPORARY: only Windows while investigating arm64 repos - # test_macos: - # name: Test macOS - # uses: ./.github/workflows/test-macos.yml - # secrets: inherit + test_macos: + name: Test macOS + uses: ./.github/workflows/test-macos.yml + secrets: inherit test_windows: name: Test Windows uses: ./.github/workflows/test-windows.yml secrets: inherit - # test_linux: - # name: Test Linux - # uses: ./.github/workflows/test-linux.yml - # secrets: inherit + test_linux: + name: Test Linux + uses: ./.github/workflows/test-linux.yml + secrets: inherit - # test_fuzz: - # name: Test Fuzz - # uses: ./.github/workflows/test-fuzz.yml - # secrets: inherit + test_fuzz: + name: Test Fuzz + uses: ./.github/workflows/test-fuzz.yml + secrets: inherit