From 24b70506b981eaae36cac7c20bb41045793ad7df Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 07:15:41 -0500 Subject: [PATCH 1/9] chore: use installed packages when available --- .github/workflows/install/action.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index e73dc55..bcd4a1f 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -39,10 +39,6 @@ runs: - if: inputs.use-container != 'true' uses: r-lib/actions/setup-pandoc@v2 - # Always run setup-r, even in containers. With install-r: false it - # skips the R download but still sets R_LIBS_USER, TZ, NOT_CRAN, etc. - # in GITHUB_ENV — the official way to align the environment for - # setup-r-dependencies and actions/cache. - uses: r-lib/actions/setup-r@v2 with: install-r: ${{ inputs.use-container != 'true' }} @@ -62,6 +58,22 @@ runs: mkdir -p "$lib" touch "$lib/.keep" + # When running in a pre-warmed container, point pak at the system library + # so it recognises already-installed packages instead of starting from an + # empty slate. upgrade: TRUE ensures any stale packages are refreshed. + # For bare runners keep the reproducible defaults (empty lib, no upgrade). + - id: rdeps-params + name: Compute setup-r-dependencies parameters + shell: bash + run: | + if [ "${{ inputs.use-container }}" = "true" ]; then + echo "lockfile-create-lib='.Library'" >> "$GITHUB_OUTPUT" + echo "upgrade=TRUE" >> "$GITHUB_OUTPUT" + else + echo "lockfile-create-lib=NULL" >> "$GITHUB_OUTPUT" + echo "upgrade=FALSE" >> "$GITHUB_OUTPUT" + fi + - uses: r-lib/actions/setup-r-dependencies@v2 env: GITHUB_PAT: ${{ inputs.token }} @@ -69,6 +81,8 @@ runs: needs: ${{ inputs.needs }} extra-packages: ${{ inputs.extra-packages }} cache-version: ${{ inputs.cache-version }} + lockfile-create-lib: ${{ steps.rdeps-params.outputs.lockfile-create-lib }} + upgrade: ${{ steps.rdeps-params.outputs.upgrade }} - if: inputs.optional-packages != '' name: Install optional packages From a832fe7449fbfccab0a883fa528b00fb3b642585 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 07:31:21 -0500 Subject: [PATCH 2/9] fix: R `.Library` not string --- .github/workflows/install/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index bcd4a1f..e0f886f 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -67,7 +67,7 @@ runs: shell: bash run: | if [ "${{ inputs.use-container }}" = "true" ]; then - echo "lockfile-create-lib='.Library'" >> "$GITHUB_OUTPUT" + echo "lockfile-create-lib=.Library" >> "$GITHUB_OUTPUT" echo "upgrade=TRUE" >> "$GITHUB_OUTPUT" else echo "lockfile-create-lib=NULL" >> "$GITHUB_OUTPUT" From 4fc50a61f4b35eae53c0acf6588e4ab6ea2560aa Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 07:47:14 -0500 Subject: [PATCH 3/9] fix: use `pak::pak()` directly for container path --- .github/workflows/install/action.yml | 43 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index e0f886f..44ed875 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -58,31 +58,40 @@ runs: mkdir -p "$lib" touch "$lib/.keep" - # When running in a pre-warmed container, point pak at the system library - # so it recognises already-installed packages instead of starting from an - # empty slate. upgrade: TRUE ensures any stale packages are refreshed. - # For bare runners keep the reproducible defaults (empty lib, no upgrade). - - id: rdeps-params - name: Compute setup-r-dependencies parameters - shell: bash + # Container path: call pak::pak() directly so it checks .libPaths() and + # only installs/updates packages that are genuinely missing or outdated. + # setup-r-dependencies always runs a full lockfile-create → lockfile-install + # cycle which reinstalls everything even when the container already has it. + - if: inputs.use-container == 'true' + name: Update R package dependencies (container) + shell: Rscript {0} + env: + GITHUB_PAT: ${{ inputs.token }} run: | - if [ "${{ inputs.use-container }}" = "true" ]; then - echo "lockfile-create-lib=.Library" >> "$GITHUB_OUTPUT" - echo "upgrade=TRUE" >> "$GITHUB_OUTPUT" - else - echo "lockfile-create-lib=NULL" >> "$GITHUB_OUTPUT" - echo "upgrade=FALSE" >> "$GITHUB_OUTPUT" - fi + # Replicate needs → Config/Needs/ expansion from r-lib/actions + needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]] + needs_parts <- needs_parts[nzchar(needs_parts)] + needs <- sprintf("Config/Needs/%s", needs_parts) + + extra_deps <- strsplit("${{ inputs.extra-packages }}", "[[:space:],]+")[[1]] + extra_deps <- extra_deps[nzchar(extra_deps)] - - uses: r-lib/actions/setup-r-dependencies@v2 + pak::pak( + c("deps::.", extra_deps), + dependencies = c(needs, "all"), + upgrade = TRUE, + ask = FALSE + ) + + # Non-container path: standard setup-r-dependencies flow with caching. + - if: inputs.use-container != 'true' + uses: r-lib/actions/setup-r-dependencies@v2 env: GITHUB_PAT: ${{ inputs.token }} with: needs: ${{ inputs.needs }} extra-packages: ${{ inputs.extra-packages }} cache-version: ${{ inputs.cache-version }} - lockfile-create-lib: ${{ steps.rdeps-params.outputs.lockfile-create-lib }} - upgrade: ${{ steps.rdeps-params.outputs.upgrade }} - if: inputs.optional-packages != '' name: Install optional packages From fbf4c2131ceef99aff7ba9a4228335c3f433a159 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 07:48:40 -0500 Subject: [PATCH 4/9] fix: revert container use for copilot --- .github/workflows/copilot-setup-steps.yml | 2 -- NEWS.md | 1 - inst/templates/workflows/copilot-setup-steps.yml | 2 -- 3 files changed, 5 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 7830bdb..d5dffcf 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -15,7 +15,6 @@ jobs: # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. copilot-setup-steps: runs-on: ubuntu-latest - container: ghcr.io/api2r/pkgskills-ci:release permissions: contents: read @@ -25,7 +24,6 @@ jobs: - uses: ./.github/workflows/install with: - use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} cache-version: copilot needs: build, check, website diff --git a/NEWS.md b/NEWS.md index 782bd2a..30d50a2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,5 @@ # pkgskills (development version) -* `use_github_copilot()` now installs a container-based `copilot-setup-steps.yml` for faster agent startup (#67). * `AGENTS.md` and `tdd-workflow` skill instructions now explicitly explain how to determine the GitHub issue number and warn agents never to guess or invent one (@copilot, #61). * `use_agent()` template is reconciled with this package's `AGENTS.md` file (#59). * `tdd-workflow` skill now documents `stbl::expect_pkg_error_snapshot()` with an explicit `package` argument instead of a helper-defined version (#51). diff --git a/inst/templates/workflows/copilot-setup-steps.yml b/inst/templates/workflows/copilot-setup-steps.yml index fb03545..e299ee3 100644 --- a/inst/templates/workflows/copilot-setup-steps.yml +++ b/inst/templates/workflows/copilot-setup-steps.yml @@ -15,7 +15,6 @@ jobs: # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. copilot-setup-steps: runs-on: ubuntu-latest - container: ghcr.io/api2r/pkgskills-ci:release permissions: contents: read @@ -25,7 +24,6 @@ jobs: - uses: ./.github/workflows/install with: - use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} cache-version: copilot needs: build, check, website From a39937b50d38ccb82e9e64701712b1029311a002 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 07:57:34 -0500 Subject: [PATCH 5/9] fix: avoid rust reinstall --- .github/docker/Dockerfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile index 9b650f3..d522b2a 100644 --- a/.github/docker/Dockerfile +++ b/.github/docker/Dockerfile @@ -53,9 +53,13 @@ ENV LANG=en_US.UTF-8 RUN git config --global --add safe.directory '*' -# Install latest stable Rust toolchain (rocker images may ship an outdated version) -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -ENV PATH="/root/.cargo/bin:${PATH}" +# Install latest stable Rust toolchain into fixed, $HOME-independent paths so +# that pak can find cargo regardless of the HOME GitHub Actions injects at runtime. +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path +ENV PATH="/usr/local/cargo/bin:${PATH}" # Rocker images configure repos to PPM's Linux binary endpoint, which doesn't # index packages needing non-standard build tools (e.g. Rust). Add CRAN source From 06b616d1287f8f296d1b1859494d5dc8a1bf8b8f Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 08:27:54 -0500 Subject: [PATCH 6/9] fix: make rustup happy --- .github/workflows/install/action.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index 44ed875..5e5f392 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -58,6 +58,22 @@ runs: mkdir -p "$lib" touch "$lib/.keep" + # GHA sets HOME=/github/home but the container process runs as root + # (euid home = /root). rustup-init.sh fails two checks because of this: + # 1. It finds cargo in PATH but looks in $HOME/.cargo (not $CARGO_HOME) + # to verify the installation is rustup-managed → "cannot install" error. + # 2. It explicitly compares $HOME with getpwuid(euid) → "$HOME differs" error. + # Exporting the real paths via GITHUB_ENV lets rustup-init use CARGO_HOME / + # RUSTUP_HOME for detection; HOME=/root is set per-step (below) to silence + # the euid check. Once confirmed, move this step into the container image. + - if: inputs.use-container == 'true' + name: Configure Rust environment for GHA container + shell: bash + run: | + echo "RUSTUP_HOME=/usr/local/rustup" >> "$GITHUB_ENV" + echo "CARGO_HOME=/usr/local/cargo" >> "$GITHUB_ENV" + echo "/usr/local/cargo/bin" >> "$GITHUB_PATH" + # Container path: call pak::pak() directly so it checks .libPaths() and # only installs/updates packages that are genuinely missing or outdated. # setup-r-dependencies always runs a full lockfile-create → lockfile-install @@ -67,6 +83,8 @@ runs: shell: Rscript {0} env: GITHUB_PAT: ${{ inputs.token }} + # Match HOME to the actual euid home so rustup-init.sh passes its checks. + HOME: /root run: | # Replicate needs → Config/Needs/ expansion from r-lib/actions needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]] From 91a46d50d178793cfdff05dea8589be1309ebe53 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 08:51:07 -0500 Subject: [PATCH 7/9] fix: more attempts at rust --- .github/workflows/install/action.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index 5e5f392..cb98a9d 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -58,21 +58,17 @@ runs: mkdir -p "$lib" touch "$lib/.keep" - # GHA sets HOME=/github/home but the container process runs as root - # (euid home = /root). rustup-init.sh fails two checks because of this: - # 1. It finds cargo in PATH but looks in $HOME/.cargo (not $CARGO_HOME) - # to verify the installation is rustup-managed → "cannot install" error. - # 2. It explicitly compares $HOME with getpwuid(euid) → "$HOME differs" error. - # Exporting the real paths via GITHUB_ENV lets rustup-init use CARGO_HOME / - # RUSTUP_HOME for detection; HOME=/root is set per-step (below) to silence - # the euid check. Once confirmed, move this step into the container image. + # pkgdepends checks `dpkg -s cargo` (not Sys.which) to decide whether Rust + # needs to be installed. Because rustup bypasses apt, dpkg has no record of + # it and pak always runs the rustup installer. Add stub entries so the + # pre-flight check passes. Once confirmed working, move this into the + # container image (Dockerfile) instead. - if: inputs.use-container == 'true' - name: Configure Rust environment for GHA container + name: Register Rust with dpkg shell: bash run: | - echo "RUSTUP_HOME=/usr/local/rustup" >> "$GITHUB_ENV" - echo "CARGO_HOME=/usr/local/cargo" >> "$GITHUB_ENV" - echo "/usr/local/cargo/bin" >> "$GITHUB_PATH" + printf '\nPackage: rustc\nStatus: install ok installed\nArchitecture: amd64\nVersion: 99.0.0\nDescription: stub; real Rust managed by rustup\n\nPackage: cargo\nStatus: install ok installed\nArchitecture: amd64\nVersion: 99.0.0\nDescription: stub; real Rust managed by rustup\n\n' \ + >> /var/lib/dpkg/status # Container path: call pak::pak() directly so it checks .libPaths() and # only installs/updates packages that are genuinely missing or outdated. @@ -83,8 +79,6 @@ runs: shell: Rscript {0} env: GITHUB_PAT: ${{ inputs.token }} - # Match HOME to the actual euid home so rustup-init.sh passes its checks. - HOME: /root run: | # Replicate needs → Config/Needs/ expansion from r-lib/actions needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]] From 730839308a825ec1e5a820636f38068b26ad62b9 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 09:10:13 -0500 Subject: [PATCH 8/9] fix: try to skip rust --- .github/workflows/install/action.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index cb98a9d..a1b0050 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -58,11 +58,18 @@ runs: mkdir -p "$lib" touch "$lib/.keep" - # pkgdepends checks `dpkg -s cargo` (not Sys.which) to decide whether Rust - # needs to be installed. Because rustup bypasses apt, dpkg has no record of - # it and pak always runs the rustup installer. Add stub entries so the - # pre-flight check passes. Once confirmed working, move this into the - # container image (Dockerfile) instead. + # When CI=true (always in GHA), pkgdepends fetches the live sysreqs DB from + # r-hub/r-system-requirements. That DB's Rust rule has an unconditional + # rustup pre_install hook with an empty packages list, so the dpkg check is + # never reached and rustup always runs. + # + # Fix: disable the live DB download so pkgdepends falls back to its bundled + # DB, which uses a proper dpkg-based check (packages: ["rustc","cargo"]). + # Register those package names as stub dpkg entries so the check passes. + # + # Once confirmed working, move both of these into the Dockerfile: + # • the printf stub → after the rustup RUN + # • PKG_SYSREQS_DB_UPDATE=false → as an ENV line at the end - if: inputs.use-container == 'true' name: Register Rust with dpkg shell: bash @@ -79,6 +86,7 @@ runs: shell: Rscript {0} env: GITHUB_PAT: ${{ inputs.token }} + PKG_SYSREQS_DB_UPDATE: "false" run: | # Replicate needs → Config/Needs/ expansion from r-lib/actions needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]] From 93c40f39bfe6210354eac6bc9258faf34cd56612 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 31 Mar 2026 09:15:05 -0500 Subject: [PATCH 9/9] fix: register rust slightly more properly --- .github/docker/Dockerfile | 14 ++++++++++++++ .github/workflows/install/action.yml | 20 -------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile index d522b2a..1e7a120 100644 --- a/.github/docker/Dockerfile +++ b/.github/docker/Dockerfile @@ -61,6 +61,13 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ | sh -s -- -y --no-modify-path ENV PATH="/usr/local/cargo/bin:${PATH}" +# Register Rust with dpkg so pkgdepends' bundled sysreqs DB treats it as +# already installed and skips any apt/rustup install attempts at runtime. +RUN RUSTC_VER=$(rustc --version | awk '{print $2}') \ + && CARGO_VER=$(cargo --version | awk '{print $2}') \ + && printf 'Package: rustc\nStatus: install ok installed\nArchitecture: amd64\nVersion: %s\nDescription: stub; real Rust managed by rustup\n\nPackage: cargo\nStatus: install ok installed\nArchitecture: amd64\nVersion: %s\nDescription: stub; real Rust managed by rustup\n\n' \ + "$RUSTC_VER" "$CARGO_VER" >> /var/lib/dpkg/status + # Rocker images configure repos to PPM's Linux binary endpoint, which doesn't # index packages needing non-standard build tools (e.g. Rust). Add CRAN source # as a permanent fallback so pak can always resolve the full CRAN catalogue. @@ -75,6 +82,13 @@ RUN Rscript -e 'install.packages("pak")' COPY install-packages.R /tmp/install-packages.R RUN Rscript /tmp/install-packages.R && rm /tmp/install-packages.R +# Use pkgdepends' bundled sysreqs DB at runtime rather than downloading the +# live r-hub DB. The live DB's Rust rule runs rustup unconditionally (empty +# packages list bypasses the dpkg check), which fails inside GHA containers +# where HOME is remapped. The bundled DB uses a proper dpkg-based check. +# Rebuild the image with a newer pkgdepends to pick up bundled DB updates. +ENV PKG_SYSREQS_DB_UPDATE=false + # GitHub Copilot's injected "Prepare Copilot" step uses `set -o pipefail` via # `shell: sh`, which dash (Debian's default /bin/sh) doesn't support. Redirect # /bin/sh to bash, which is already installed and is a superset of POSIX sh. diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index a1b0050..44ed875 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -58,25 +58,6 @@ runs: mkdir -p "$lib" touch "$lib/.keep" - # When CI=true (always in GHA), pkgdepends fetches the live sysreqs DB from - # r-hub/r-system-requirements. That DB's Rust rule has an unconditional - # rustup pre_install hook with an empty packages list, so the dpkg check is - # never reached and rustup always runs. - # - # Fix: disable the live DB download so pkgdepends falls back to its bundled - # DB, which uses a proper dpkg-based check (packages: ["rustc","cargo"]). - # Register those package names as stub dpkg entries so the check passes. - # - # Once confirmed working, move both of these into the Dockerfile: - # • the printf stub → after the rustup RUN - # • PKG_SYSREQS_DB_UPDATE=false → as an ENV line at the end - - if: inputs.use-container == 'true' - name: Register Rust with dpkg - shell: bash - run: | - printf '\nPackage: rustc\nStatus: install ok installed\nArchitecture: amd64\nVersion: 99.0.0\nDescription: stub; real Rust managed by rustup\n\nPackage: cargo\nStatus: install ok installed\nArchitecture: amd64\nVersion: 99.0.0\nDescription: stub; real Rust managed by rustup\n\n' \ - >> /var/lib/dpkg/status - # Container path: call pak::pak() directly so it checks .libPaths() and # only installs/updates packages that are genuinely missing or outdated. # setup-r-dependencies always runs a full lockfile-create → lockfile-install @@ -86,7 +67,6 @@ runs: shell: Rscript {0} env: GITHUB_PAT: ${{ inputs.token }} - PKG_SYSREQS_DB_UPDATE: "false" run: | # Replicate needs → Config/Needs/ expansion from r-lib/actions needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]]