diff --git a/.github/workflows/revdeps.yml b/.github/workflows/revdeps.yml new file mode 100644 index 000000000..6dcae2412 --- /dev/null +++ b/.github/workflows/revdeps.yml @@ -0,0 +1,291 @@ +--- + +name: Reverse Dependencies +run-name: Reverse Dependencies (${{ inputs.package }}@${{ github.sha }}) +on: + workflow_dispatch: + inputs: + package: + description: 'Check rstan or just StanHeaders (default is rstan)' + required: false + default: 'rstan' + type: choice + options: + - rstan + - StanHeaders + batch-size: + description: 'Number of packages to check in each batch (default is 10)' + required: false + default: 10 + type: number + pkg-source: + description: 'Source to use for rstan/StanHeaders (either a git ref or "CRAN", default is develop)' + required: false + default: 'develop' + type: string + rstantools-source: + description: Source to use for rstantools (either a git ref or "CRAN", default is CRAN)' + required: false + default: 'CRAN' + type: string + run-donttest: + description: 'Run `donttest` examples for each package (increasing check time), default is false' + required: false + default: false + type: boolean + +jobs: + prepare-library: + runs-on: ubuntu-latest + name: Build & Cache Package Dependencies + outputs: + deps_matrix: ${{ steps.dep-batches.outputs.deps_matrix }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + repository: stan-dev/rstan + ref: ${{ (inputs.pkg-source == 'CRAN' || inputs.pkg-source == 'cran') && 'develop' || inputs.pkg-source }} + + + - name: Restore cached INLA source + id: cache-inla + uses: actions/cache@v6 + with: + path: /tmp/INLA_25.10.19.tar.gz + key: inla-25.10.19 + + - name: Download INLA source if not cached + if: steps.cache-inla.outputs.cache-hit != 'true' + run: | + wget https://inla.r-inla-download.org/R/stable/src/contrib/INLA_25.10.19.tar.gz -O /tmp/INLA_25.10.19.tar.gz + + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: 'release' + + - name: Download recursive dependencies, batch dependencies to check + id: dep-batches + run: | + batch_size <- ${{ inputs.batch-size }} + pkg <- "${{ inputs.package }}" + # Parallelise building as much as possible + options(Ncpus = parallel::detectCores()) + Sys.setenv(MAKEFLAGS = paste0("-j", parallel::detectCores())) + # Initialise a standalone library + dir.create("/tmp/PakLibrary") + .libPaths(c("/tmp/PakLibrary", .libPaths())) + system("sudo apt-get install -y libcurl4-openssl-dev") + install.packages(c("pak", "rstantools")) + # Lookup and (attempt to) install all dependencies for reverse-dependencies + pkg_deps <- tools::package_dependencies(pkg, reverse = TRUE)[[pkg]] + rec_deps <- unique(do.call(c, tools::package_dependencies(pkg_deps, which = 'all'))) + install.packages(rec_deps) + # Remaining packages will be bioconductor or other non-CRAN packages, install manually + rec_deps <- rec_deps[!(rec_deps %in% installed.packages()[,"Package"])] + rec_deps <- rec_deps[!(rec_deps %in% c("blavsam", "eggCountsExtra", "BRugs", "cmdstanr", "INLA"))] + + pak::pak(c( + rec_deps, + "github::stan-dev/cmdstanr", + "github::ecmerkle/blavsam", + "github::CraigWangStat/eggCountsExtra", + "local::/tmp/INLA_25.10.19.tar.gz", + "any::qgraph" + )) + # Install dev versions last so that they're not used for any dependencies + pak::pak("local::./StanHeaders") + if (pkg == "rstan") { + pak::pak("local::./rstan/rstan") + } + rstantools_src <- "${{ inputs.rstantools-source || 'CRAN'}}" + if (tolower(rstantools_src) != "cran") { + pak::pak(paste0("stan-dev/rstantools@", rstantools_src)) + } + # Split dependencies into requested batch size and + # restructure into format for actions 'matrix' argument + curr_deps <- split(pkg_deps, ceiling(seq_along(pkg_deps) / batch_size)) + deps_matrix <- list() + for (i in seq_along(curr_deps)) { + deps_matrix[[i]] <- list(group = i, deps = jsonlite::toJSON(curr_deps[[i]])) + } + cat(paste0("deps_matrix=", jsonlite::toJSON(list(include = deps_matrix), auto_unbox = TRUE)), + file=Sys.getenv("GITHUB_OUTPUT"), append = TRUE) + shell: Rscript {0} + + - name: Cache built packages + uses: actions/cache/save@v6 + with: + path: /tmp/PakLibrary/ + key: ${{ github.run_id }} + + reverse-dependencies: + needs: prepare-library + runs-on: ubuntu-latest + name: Check Reverse Dependencies (Batch ${{ matrix.group }}) + strategy: + fail-fast: false + max-parallel: 10 + matrix: ${{ fromJson(needs.prepare-library.outputs.deps_matrix) }} + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + _R_CHECK_FORCE_SUGGESTS_: false + R_LIBS_USER: /tmp/PakLibrary + NOT_CRAN: + _R_CHECK_CRAN_INCOMING_: false + _R_CHECK_CRAN_INCOMING_REMOTE_: false + _R_CHECK_DONTTEST_EXAMPLES_: ${{ inputs.run-donttest }} + _R_CHECK_ELAPSED_TIMEOUT_: 1h + _R_CHECK_SYSTEM_CLOCK_: 0 + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Restore built library + uses: actions/cache/restore@v6 + with: + path: /tmp/PakLibrary/ + key: ${{ github.run_id }} + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: 'release' + + - uses: r-lib/actions/setup-pandoc@v2 + - uses: quarto-dev/quarto-actions/setup@v2 + + - name: Configure Makevars + run: | + mkdir -p ~/.R + cat < ~/.R/Makevars + # Eigen warnings significantly bloat log size, so ignore + CXXFLAGS += -w + CXX17FLAGS += -w + EOF + + - name: Install system dependencies + run: | + sudo apt-get install -y pandoc libudunits2-dev libglpk-dev libgsl0-dev \ + texlive-latex-base texlive-latex-recommended texlive-fonts-extra \ + texlive-fonts-recommended texlive-luatex texlive-latex-extra \ + texlive-bibtex-extra + + - name: Run reverse-dependency checks + env: + R_LIBS_USER: /tmp/PakLibrary + NOT_CRAN: + _R_CHECK_CRAN_INCOMING_: false + _R_CHECK_CRAN_INCOMING_REMOTE_: false + _R_CHECK_DONTTEST_EXAMPLES_: ${{ inputs.run-donttest }} + _R_CHECK_ELAPSED_TIMEOUT_: 1h + _R_CHECK_SYSTEM_CLOCK_: 0 + run: | + curr_deps <- jsonlite::fromJSON('${{ matrix.deps }}') + # Install any system dependencies needed for packages + rec_deps <- unique(do.call(c, tools::package_dependencies(curr_deps, which = 'all'))) + pak::sysreqs_fix_installed(c(curr_deps, rec_deps)) + dir.create("testdir") + download.packages(curr_deps, "testdir", repos = "https://cran.rstudio.com") + Sys.unsetenv("NOT_CRAN") + tools::check_packages_in_dir("testdir", check_args = "--as-cran --no-manual", Ncpus = parallel::detectCores() - 1) + shell: Rscript {0} + + - uses: actions/upload-artifact@v7 + with: + name: checkresults-${{ matrix.group }} + path: | + testdir/**/00check.log + testdir/**/00install.out + compression-level: 9 + retention-days: 1 + + summarise-results: + name: Summarise Results + needs: reverse-dependencies + runs-on: ubuntu-latest + + steps: + - name: Download all results + uses: actions/download-artifact@v7 + with: + path: checkresults + pattern: checkresults-* + merge-multiple: true + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: 'release' + + - name: Generate results summary output + run: | + setwd("checkresults") + install.packages("knitr") + res <- tools::check_packages_in_dir_details(".") + all_pkgs <- gsub("^\\./(.*)\\.Rcheck", "\\1", list.dirs(".", recursive = FALSE)) + install_failures <- unique(res$Package[res$Check == "whether package can be installed"]) + install_failures <- install_failures[order(install_failures)] + check_statuses <- c("tests", "re-building of vignette outputs", "examples", "examples with --run-donttest") + check_failures <- unique(res$Package[res$Check %in% check_statuses]) + check_failures <- check_failures[order(check_failures)] + summary_tbl <- knitr::kable( + data.frame( + "OK" = length(setdiff(all_pkgs, c(install_failures, check_failures))), + "Install Failed" = length(install_failures), + "Tests/Vignettes/Examples Failed" = length(check_failures), + check.names = FALSE + ) + ) + install_logs <- sapply(install_failures, \(pkg) { + paste0(c( + paste("###", pkg), + "
", + "Install Log", + "", + "```", + paste0(readLines(paste0(pkg, ".Rcheck/00install.out")), collapse = "\n"), + "```", + "", + "
\n"), + collapse = "\n" + ) + }) |> + paste0(collapse = "\n") + check_logs <- sapply(check_failures, \(pkg) { + paste0(c( + "", + paste("###", pkg), + "", + "
", + "Check Log", + "", + "```", + paste0(readLines(paste0(pkg, ".Rcheck/00check.log")), collapse = "\n"), + "```", + "", + "
\n"), + collapse = "\n" + ) + }) |> + paste0(collapse = "\n") + result_md <- paste0(c( + "# Reverse-Dependency Status\n", + "## Summary\n", + summary_tbl, + "## Install Failure\n", + install_logs, + "## Vignette/Test Failures\n", + check_logs), + collapse = "\n" + ) + cat(result_md, file = Sys.getenv("GITHUB_STEP_SUMMARY"), append = TRUE) + shell: Rscript {0} + + - name: Upload combined results + uses: actions/upload-artifact@v7 + with: + name: all-logs + path: checkresults + compression-level: 9