Skip to content

Dependency floor checks #10

Dependency floor checks

Dependency floor checks #10

name: Dependency floor checks
on:
workflow_dispatch:
inputs:
r_version:
description: "R version to test. Use the latest patch release for the minimum supported R minor version."
required: true
default: "4.1.3"
install_suggests:
description: "Install Suggests too. Disable only for a faster import-only smoke check."
required: false
type: boolean
default: true
permissions:
contents: read
concurrency:
group: dependency-floor-${{ github.ref }}
cancel-in-progress: false
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
_R_CHECK_FORCE_SUGGESTS_: ${{ inputs.install_suggests }}
DEPENDENCY_FLOOR_CACHE_VERSION: v3
RSPM: https://packagemanager.posit.co/cran/__linux__/focal/latest
R_LIBS: ${{ github.workspace }}/.cache/r-library
R_LIBS_USER: ${{ github.workspace }}/.cache/r-library
EUNOMIA_DATA_FOLDER: ${{ github.workspace }}/.cache/eunomia
UV_CACHE_DIR: ${{ github.workspace }}/.cache/uv
RETICULATE_PY_VERSION: 3.11
jobs:
dependency-floor:
runs-on: ubuntu-latest
container: rocker/r-ver:${{ inputs.r_version }}
steps:
- name: Checkout repo
uses: actions/checkout@v5
- name: Install system requirements
run: |
apt-get update
apt-get install -y --no-install-recommends \
build-essential \
cmake \
curl \
default-jdk \
git \
libbz2-dev \
libcurl4-openssl-dev \
libfontconfig1-dev \
libfreetype6-dev \
libfribidi-dev \
libgit2-dev \
libharfbuzz-dev \
libicu-dev \
libjpeg-dev \
liblzma-dev \
libomp-dev \
libpng-dev \
libssh-dev \
libssl-dev \
libtiff5-dev \
libwebp-dev \
libxml2-dev \
make \
pandoc \
pkg-config \
python3 \
python3-pip \
python3-venv \
zlib1g-dev
R CMD javareconf
- name: Prepare caches
run: |
mkdir -p "${R_LIBS_USER}" "${EUNOMIA_DATA_FOLDER}" "${UV_CACHE_DIR}"
- name: Restore R library cache
id: cache-r-library-restore
uses: actions/cache/restore@v5
with:
path: ${{ env.R_LIBS_USER }}
key: dependency-floor-${{ env.DEPENDENCY_FLOOR_CACHE_VERSION }}-focal-r${{ inputs.r_version }}-suggests-${{ inputs.install_suggests }}-${{ hashFiles('DESCRIPTION') }}
restore-keys: |
dependency-floor-${{ env.DEPENDENCY_FLOOR_CACHE_VERSION }}-focal-r${{ inputs.r_version }}-suggests-${{ inputs.install_suggests }}-
- name: Cache Eunomia datasets
uses: actions/cache@v5
with:
path: ${{ env.EUNOMIA_DATA_FOLDER }}
key: eunomia-v1-${{ runner.os }}-GiBleed_5.3
- name: Cache uv
uses: actions/cache@v5
with:
path: ${{ env.UV_CACHE_DIR }}
key: uv-v1-${{ runner.os }}-py${{ env.RETICULATE_PY_VERSION }}
- name: Install dependency floor
shell: Rscript {0}
env:
INSTALL_SUGGESTS: ${{ inputs.install_suggests }}
run: |
.libPaths(c(Sys.getenv("R_LIBS_USER"), .libPaths()))
message("R library paths: ", paste(.libPaths(), collapse = ", "))
repos <- Sys.getenv("RSPM", unset = "https://cloud.r-project.org")
install.packages(c("remotes", "rcmdcheck"), repos = repos)
options(Ncpus = max(1L, parallel::detectCores()))
installSuggests <- tolower(Sys.getenv("INSTALL_SUGGESTS", unset = "false")) == "true"
description <- read.dcf("DESCRIPTION")[1, ]
hardDependencyFields <- intersect(c("Depends", "Imports", "LinkingTo"), names(description))
dependencyFields <- hardDependencyFields
suggestedPackages <- character()
dependencyFields <- intersect(dependencyFields, names(description))
message("Installing dependency fields: ", paste(dependencyFields, collapse = ", "))
parseDependencies <- function(fields) {
dependencyText <- paste(description[fields], collapse = ",")
dependencyText <- gsub("[\r\n]", " ", dependencyText)
dependencies <- trimws(unlist(strsplit(dependencyText, ",")))
dependencies <- dependencies[nzchar(dependencies)]
parsed <- regexec("^([A-Za-z0-9.]+)\\s*(?:\\(([^ ]+)\\s*([^)]+)\\))?$", dependencies)
parsed <- regmatches(dependencies, parsed)
parsed <- do.call(rbind, lapply(parsed, function(x) {
length(x) <- 4
data.frame(
package = x[2],
operator = x[3],
version = x[4],
stringsAsFactors = FALSE
)
}))
parsed[parsed$package != "R", ]
}
parsed <- parseDependencies(dependencyFields)
if (installSuggests && "Suggests" %in% names(description)) {
suggested <- parseDependencies("Suggests")
suggestedPackages <- suggested$package
parsed <- rbind(parsed, suggested)
}
floorDependencies <- parsed[parsed$operator == ">=", ]
write.csv(floorDependencies, "dependency-floor-requested.csv", row.names = FALSE)
if (installSuggests) {
compatibilityPins <- data.frame(
package = c("Matrix", "arrow", "pbkrtest", "xgboost"),
version = c("1.6-5", "17.0.0", "0.5.2", "1.7.8.1"),
stringsAsFactors = FALSE
)
write.csv(compatibilityPins, "dependency-floor-compatibility-pins.csv", row.names = FALSE)
for (i in seq_len(nrow(compatibilityPins))) {
package <- compatibilityPins$package[[i]]
version <- compatibilityPins$version[[i]]
message("Installing old-R compatibility pin: ", package, " ", version)
remotes::install_version(
package = package,
version = version,
repos = repos,
dependencies = FALSE,
upgrade = "never"
)
}
} else {
write.csv(data.frame(package = character(), version = character()),
"dependency-floor-compatibility-pins.csv",
row.names = FALSE
)
}
remotes::install_deps(
dependencies = hardDependencyFields,
repos = repos,
upgrade = "never"
)
if (length(suggestedPackages) > 0) {
installedPackages <- rownames(installed.packages())
suggestedPackages <- setdiff(suggestedPackages, installedPackages)
if (length(suggestedPackages) > 0) {
message("Installing direct suggested packages: ", paste(suggestedPackages, collapse = ", "))
install.packages(
suggestedPackages,
repos = repos,
dependencies = hardDependencyFields
)
}
missingSuggestedPackages <- setdiff(suggestedPackages, rownames(installed.packages()))
if (length(missingSuggestedPackages) > 0) {
stop(
"Failed to install direct suggested packages: ",
paste(missingSuggestedPackages, collapse = ", ")
)
}
}
sourceFloorPackages <- c("Andromeda", "FeatureExtraction")
sourceRepos <- "https://cloud.r-project.org"
for (i in seq_len(nrow(floorDependencies))) {
package <- floorDependencies$package[[i]]
version <- floorDependencies$version[[i]]
useSource <- package %in% sourceFloorPackages
message(
"Installing floor dependency: ",
package,
" ",
version,
if (useSource) " from source" else ""
)
remotes::install_version(
package = package,
version = version,
repos = if (useSource) sourceRepos else repos,
dependencies = FALSE,
upgrade = "never",
type = if (useSource) "source" else getOption("pkgType")
)
}
installed <- as.data.frame(installed.packages()[, c("Package", "Version")])
declared <- parsed$package
installed <- installed[installed$Package %in% declared, ]
installed <- installed[order(tolower(installed$Package)), ]
write.csv(installed, "dependency-floor-installed.csv", row.names = FALSE)
- name: Save R library cache
if: steps.cache-r-library-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ steps.cache-r-library-restore.outputs.cache-primary-key }}
- name: Run R CMD check
shell: Rscript {0}
run: |
.libPaths(c(Sys.getenv("R_LIBS_USER"), .libPaths()))
message("R library paths: ", paste(.libPaths(), collapse = ", "))
result <- rcmdcheck::rcmdcheck(
args = c("--no-manual", "--no-build-vignettes", "--ignore-vignettes"),
build_args = c("--no-manual", "--no-build-vignettes"),
error_on = "warning",
check_dir = "check"
)
print(result)
- name: Upload results
if: always()
uses: actions/upload-artifact@v5
with:
name: dependency-floor-results
path: |
check
dependency-floor-requested.csv
dependency-floor-compatibility-pins.csv
dependency-floor-installed.csv