diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 3f1cc98b..25cb2c5e 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -11,6 +11,7 @@ name: R-CMD-check jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} + container: ${{ matrix.config.container }} name: ${{ matrix.config.os }} (${{ matrix.config.r }}) @@ -20,15 +21,16 @@ jobs: config: - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release', container: 'ghcr.io/api2r/pkgskills-ci:devel'} + - {os: ubuntu-latest, r: 'release', container: 'ghcr.io/api2r/pkgskills-ci:release'} + - {os: ubuntu-latest, r: 'oldrel-1', container: 'ghcr.io/api2r/pkgskills-ci:oldrel-1'} steps: - uses: actions/checkout@v6 - uses: ./.github/workflows/install with: + use-container: "${{ matrix.config.container != '' }}" token: ${{ secrets.GITHUB_TOKEN }} r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 2fca7101..1e644a87 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -29,8 +29,8 @@ jobs: needs: build, check, website # Extra packages include things referenced in skills, to make sure the # agent has them available. + optional-packages: any::astgrepr extra-packages: >- - any::astgrepr any::cli any::covr any::devtools @@ -44,6 +44,7 @@ jobs: any::testthat any::usethis any::withr + gilead-biostats/qcthat local::. - name: Install air diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml index 95c460b9..936be1fb 100644 --- a/.github/workflows/install/action.yml +++ b/.github/workflows/install/action.yml @@ -20,26 +20,102 @@ inputs: description: "Extra packages, passed to r-lib/actions/setup-r-dependencies@v2" required: false default: any::rcmdcheck + optional-packages: + description: "Optional extra packages; installed individually, warn (not error) on failure" + required: false + default: "" cache-version: description: "Cache version for r-lib/actions/setup-r-dependencies@v2" required: false default: "1" + use-container: + description: "Set to 'true' when running inside a pre-warmed container (skips R and pandoc setup)" + required: false + default: "false" runs: using: "composite" steps: - - uses: r-lib/actions/setup-pandoc@v2 + - if: inputs.use-container != 'true' + uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 with: + install-r: ${{ inputs.use-container != 'true' }} r-version: ${{ inputs.r-version }} http-user-agent: ${{ inputs.http-user-agent }} use-public-rspm: true - - uses: r-lib/actions/setup-r-dependencies@v2 + # In pre-warmed containers every package may already be in the system + # library, so nothing is ever written to R_LIBS_USER. Ensure the + # directory exists with a sentinel so the actions/cache glob matches + # and the "Path Validation Error" warning doesn't fire at cache-save. + - if: inputs.use-container == 'true' + name: Ensure R_LIBS_USER exists + shell: bash + run: | + lib="$(Rscript -e 'cat(Sys.getenv("R_LIBS_USER"))')" + mkdir -p "$lib" + touch "$lib/.keep" + + # 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: | + # *Remove* this package from the container if present. Only relevant for stbl. + try(remove.packages("stbl"), silent = TRUE) + + # 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)] + + # Over-protect for strangeness in the container + if (!requireNamespace("pak", quietly = TRUE)) { + install.packages("pak") + } + + 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 }} + + - if: inputs.optional-packages != '' + name: Install optional packages + shell: Rscript {0} + env: + GITHUB_PAT: ${{ inputs.token }} + run: | + pkgs <- strsplit("${{ inputs.optional-packages }}", "\\s+")[[1]] + pkgs <- pkgs[nzchar(pkgs)] + for (pkg in pkgs) { + tryCatch( + pak::pak(pkg), + error = function(e) { + msg <- sprintf("Failed to install optional package %s: %s", pkg, conditionMessage(e)) + msg <- gsub("[\r\n]+", " ", msg) + cat("::warning::", msg, "\n", sep = "") + } + ) + } diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 5cfcf89c..fe42c2cf 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -14,6 +14,8 @@ name: pkgdown jobs: pkgdown: runs-on: ubuntu-latest + container: + image: ghcr.io/api2r/pkgskills-ci:release # Only restrict concurrency for non-PR jobs concurrency: group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} @@ -25,18 +27,12 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Set Development Mode Env Var - if: github.ref_name != 'main' - run: | - echo "Ref is '${{ github.ref_name }}', setting PKGDOWN_DEV_MODE=devel" - echo "PKGDOWN_DEV_MODE=devel" >> $GITHUB_ENV - - uses: ./.github/workflows/install with: + use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} needs: website extra-packages: any::pkgdown gilead-biostats/qcthat local::. - cache-version: "1" - name: Build site run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml index 1881e2a9..8c94262a 100644 --- a/.github/workflows/pr-commands.yaml +++ b/.github/workflows/pr-commands.yaml @@ -11,6 +11,8 @@ jobs: if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} name: document runs-on: ubuntu-latest + container: + image: ghcr.io/api2r/pkgskills-ci:release permissions: contents: write steps: @@ -22,10 +24,10 @@ jobs: - uses: ./.github/workflows/install with: + use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} needs: pr-document extra-packages: any::roxygen2 - cache-version: "1" - name: Document run: roxygen2::roxygenise() diff --git a/.github/workflows/qcthat.yaml b/.github/workflows/qcthat.yaml index 16695bdd..a6ea54c7 100644 --- a/.github/workflows/qcthat.yaml +++ b/.github/workflows/qcthat.yaml @@ -48,6 +48,8 @@ env: jobs: qcthat: runs-on: ubuntu-latest + container: + image: ghcr.io/api2r/pkgskills-ci:release if: >- (github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'qcthat-uat')) || github.event_name != 'issues' @@ -58,9 +60,9 @@ jobs: - uses: ./.github/workflows/install with: + use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} extra-packages: Gilead-BioStats/qcthat@main local::. - cache-version: "1" - name: Manage User Acceptance Testing if: >- diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index f4be0183..6878a499 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -11,15 +11,17 @@ name: test-coverage jobs: test-coverage: runs-on: ubuntu-latest + container: + image: ghcr.io/api2r/pkgskills-ci:release steps: - uses: actions/checkout@v6 - uses: ./.github/workflows/install with: + use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} needs: coverage extra-packages: any::covr any::xml2 - cache-version: "1" - name: Test coverage run: |