fix(build): build packages requiring newer R on compatible minors
This commit is contained in:
parent
d80d3e814e
commit
2861d50446
3 changed files with 256 additions and 0 deletions
72
local/build-newer-r.R
Normal file
72
local/build-newer-r.R
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
source(file.path("local", "newer-r-helpers.R"))
|
||||
source(file.path("local", "rebuild-missing-helpers.R"))
|
||||
|
||||
args <- commandArgs(trailingOnly = TRUE)
|
||||
pos <- args[!startsWith(args, "--")]
|
||||
if (!length(pos) %in% c(1L, 3L)) {
|
||||
stop("Usage: build-newer-r.R PRIMARY_R [SPLIT_INTO SPLIT_INDEX] [--dry-run]")
|
||||
}
|
||||
primary_r <- pos[1L]
|
||||
target_r <- as.character(getRversion())
|
||||
pkgs <- newer_r_candidates(tools::CRAN_package_db(), primary_r, target_r)
|
||||
exclude <- jsonlite::fromJSON("local/excluded-packages.json")[["package"]]
|
||||
pkgs <- pkgs[!pkgs$Package %in% exclude, , drop = FALSE]
|
||||
if (length(pos) == 3L) {
|
||||
pkgs <- pkgs[
|
||||
shard_slice(seq_len(nrow(pkgs)), pos[2L], pos[3L]),
|
||||
,
|
||||
drop = FALSE
|
||||
]
|
||||
}
|
||||
message(sprintf(
|
||||
"R %s: %d package(s) unsupported by primary R %s",
|
||||
target_r,
|
||||
nrow(pkgs),
|
||||
primary_r
|
||||
))
|
||||
if ("--dry-run" %in% args) {
|
||||
print(pkgs, row.names = FALSE)
|
||||
} else {
|
||||
options(crayon.enabled = TRUE, Ncpus = 4, future.globals.onReference = NULL)
|
||||
for (i in seq_len(nrow(pkgs))) {
|
||||
message(sprintf(
|
||||
"[%d/%d] %s %s",
|
||||
i,
|
||||
nrow(pkgs),
|
||||
pkgs$Package[i],
|
||||
pkgs$Version[i]
|
||||
))
|
||||
bincraft::build_binary_package(
|
||||
pkgs$Package[i],
|
||||
tag = pkgs$Version[i],
|
||||
# Keep bincraft's normal ABI routing: safe binaries can be shared;
|
||||
# sensitive ones use this interpreter's minor for presence and output.
|
||||
is_r_minor_sensitive = bincraft::classify_r_minor_sensitive(
|
||||
pkgs$Package[i],
|
||||
pkgs$Version[i],
|
||||
metadata_db_host = "r-binaries.devxy.io",
|
||||
metadata_db_name = "build_metadata",
|
||||
metadata_db_user = "rpkgs",
|
||||
metadata_db_password = Sys.getenv("PGPASS"),
|
||||
metadata_db_sslmode = "require",
|
||||
metadata_db_port = 15432
|
||||
),
|
||||
s3_endpoint = "https://s3.eu-central-003.backblazeb2.com",
|
||||
s3_region = "eu-central-003",
|
||||
s3_bucket = "devxy-rpkgs-binaries",
|
||||
s3_access_key_id = Sys.getenv("B2_S3_ACCESS_KEY"),
|
||||
s3_secret_access_key = Sys.getenv("B2_S3_SECRET_KEY"),
|
||||
metadata_db_host = "r-binaries.devxy.io",
|
||||
metadata_db_name = "build_metadata",
|
||||
metadata_db_table = "single_builds",
|
||||
metadata_db_user = "rpkgs",
|
||||
metadata_db_password = Sys.getenv("PGPASS"),
|
||||
metadata_db_sslmode = "require",
|
||||
metadata_db_port = 15432,
|
||||
archive = TRUE,
|
||||
patches = "local/patches",
|
||||
upload = TRUE,
|
||||
store_build_metadata = TRUE
|
||||
)
|
||||
}
|
||||
}
|
||||
75
local/newer-r-helpers.R
Normal file
75
local/newer-r-helpers.R
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Evaluate every R constraint, including patch-level and upper bounds.
|
||||
r_requirement_satisfied <- function(depends, r_version) {
|
||||
if (is.na(depends) || !nzchar(trimws(depends))) {
|
||||
return(TRUE)
|
||||
}
|
||||
entries <- trimws(strsplit(depends, ",", fixed = TRUE)[[1L]])
|
||||
entries <- entries[grepl("^R(\\s|\\(|$)", entries)]
|
||||
all(vapply(
|
||||
entries,
|
||||
function(entry) {
|
||||
if (entry == "R") {
|
||||
return(TRUE)
|
||||
}
|
||||
match <- regmatches(
|
||||
entry,
|
||||
regexec(
|
||||
"^R\\s*\\(\\s*(>=|<=|==|>|<)\\s*([0-9]+(?:[.-][0-9]+)*)\\s*\\)$",
|
||||
entry,
|
||||
perl = TRUE
|
||||
)
|
||||
)[[1L]]
|
||||
if (length(match) != 3L) {
|
||||
stop("Cannot parse R dependency: ", entry)
|
||||
}
|
||||
comparison <- utils::compareVersion(r_version, match[3L])
|
||||
switch(
|
||||
match[2L],
|
||||
">=" = comparison >= 0L,
|
||||
"<=" = comparison <= 0L,
|
||||
"==" = comparison == 0L,
|
||||
">" = comparison > 0L,
|
||||
"<" = comparison < 0L
|
||||
)
|
||||
},
|
||||
logical(1L)
|
||||
))
|
||||
}
|
||||
|
||||
# Use unfiltered current CRAN metadata: available.packages() would hide exactly
|
||||
# the packages that cannot run under the primary interpreter.
|
||||
# Propagate R incompatibility through required dependencies, not Suggests.
|
||||
newer_r_candidates <- function(db, primary_r, target_r) {
|
||||
db <- as.data.frame(db, stringsAsFactors = FALSE)
|
||||
# CRAN also lists R-specific Recommended copies (e.g. Matrix for R-devel).
|
||||
# Prefer the ordinary contrib record, or a future-R copy can incorrectly
|
||||
# block every package depending on Matrix on all released interpreters.
|
||||
if ("Path" %in% names(db)) {
|
||||
db <- db[order(!is.na(db$Path) & nzchar(db$Path)), , drop = FALSE]
|
||||
}
|
||||
db <- db[!duplicated(db$Package), , drop = FALSE]
|
||||
deps <- tools::package_dependencies(
|
||||
packages = db$Package,
|
||||
db = as.matrix(db),
|
||||
which = c("Depends", "Imports", "LinkingTo"),
|
||||
recursive = FALSE
|
||||
)
|
||||
supported <- function(version) {
|
||||
ok <- vapply(db$Depends, r_requirement_satisfied, logical(1L), version)
|
||||
repeat {
|
||||
blocked <- db$Package[!ok]
|
||||
next_ok <- ok &
|
||||
!vapply(deps, function(x) any(x %in% blocked), logical(1L))
|
||||
if (identical(ok, next_ok)) {
|
||||
return(ok)
|
||||
}
|
||||
ok <- next_ok
|
||||
}
|
||||
}
|
||||
selected <- !supported(primary_r) & supported(target_r)
|
||||
if ("OS_type" %in% names(db)) {
|
||||
selected <- selected & (is.na(db$OS_type) | db$OS_type != "windows")
|
||||
}
|
||||
result <- db[selected, c("Package", "Version"), drop = FALSE]
|
||||
result[order(result$Package), , drop = FALSE]
|
||||
}
|
||||
81
local/tests/test-newer-r-helpers.R
Normal file
81
local/tests/test-newer-r-helpers.R
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
source(file.path("..", "newer-r-helpers.R"))
|
||||
|
||||
test_that("R constraints respect patch versions, bounds, and missing fields", {
|
||||
expect_true(r_requirement_satisfied(NA_character_, "4.4.3"))
|
||||
expect_true(r_requirement_satisfied(
|
||||
"Matrix (>= 1.4-0), R (>= 4.5.0)",
|
||||
"4.5.3"
|
||||
))
|
||||
expect_false(r_requirement_satisfied("R (>= 4.5.0)", "4.4.3"))
|
||||
expect_false(r_requirement_satisfied("R (>= 4.5.4)", "4.5.3"))
|
||||
expect_true(r_requirement_satisfied("R (>= 4.5), R (< 4.6)", "4.5.3"))
|
||||
expect_false(r_requirement_satisfied("R (>= 4.5), R (< 4.6)", "4.6.0"))
|
||||
expect_true(r_requirement_satisfied("R (== 4.5.3)", "4.5.3"))
|
||||
expect_true(r_requirement_satisfied("R (> 4.5.2), R (<= 4.5.3)", "4.5.3"))
|
||||
expect_true(r_requirement_satisfied("R, Rcpp (>= 1.0)", "4.4.3"))
|
||||
expect_error(r_requirement_satisfied("R (~ 4.5)", "4.5.3"), "Cannot parse")
|
||||
})
|
||||
|
||||
fixture <- function() {
|
||||
data.frame(
|
||||
Package = c(
|
||||
"arules",
|
||||
"arulesSequences",
|
||||
"indirect",
|
||||
"optional",
|
||||
"ordinary",
|
||||
"future",
|
||||
"blocked"
|
||||
),
|
||||
Version = rep("1.0", 7L),
|
||||
Depends = c(
|
||||
"R (>= 4.5.0)",
|
||||
"arules",
|
||||
NA,
|
||||
NA,
|
||||
"R (>= 4.0)",
|
||||
"R (>= 4.7)",
|
||||
NA
|
||||
),
|
||||
Imports = c(NA, NA, "arulesSequences", NA, NA, NA, "arules, future"),
|
||||
LinkingTo = NA_character_,
|
||||
Suggests = c(NA, NA, NA, "arules", NA, NA, NA)
|
||||
)
|
||||
}
|
||||
|
||||
test_that("every compatible newer minor includes required dependents only", {
|
||||
db <- fixture()
|
||||
for (target in c("4.5.3", "4.6.0")) {
|
||||
expect_identical(
|
||||
newer_r_candidates(db, "4.4.3", target)$Package,
|
||||
c("arules", "arulesSequences", "indirect")
|
||||
)
|
||||
}
|
||||
expect_equal(nrow(newer_r_candidates(db, "4.4.3", "4.4.3")), 0L)
|
||||
expect_equal(nrow(newer_r_candidates(db, "4.5.3", "4.4.3")), 0L)
|
||||
expect_equal(nrow(newer_r_candidates(db, "4.5.3", "4.6.0")), 0L)
|
||||
})
|
||||
|
||||
test_that("LinkingTo, dependency cycles, Windows and empty metadata are handled", {
|
||||
db <- fixture()
|
||||
db$LinkingTo[5L] <- "arules"
|
||||
db$Imports[1L] <- "ordinary"
|
||||
db$OS_type <- c(NA, "windows", rep(NA_character_, 5L))
|
||||
expect_identical(
|
||||
newer_r_candidates(db, "4.4.3", "4.6.0")$Package,
|
||||
c("arules", "indirect", "ordinary")
|
||||
)
|
||||
expect_equal(nrow(newer_r_candidates(db[FALSE, ], "4.4.3", "4.6.0")), 0L)
|
||||
})
|
||||
|
||||
test_that("R-devel Recommended copies cannot block ordinary CRAN dependencies", {
|
||||
db <- fixture()
|
||||
db$Path <- NA_character_
|
||||
future_copy <- db[1L, ]
|
||||
future_copy$Depends <- "R (>= 4.7)"
|
||||
future_copy$Path <- "4.7.0/Recommended"
|
||||
expect_identical(
|
||||
newer_r_candidates(rbind(future_copy, db), "4.4.3", "4.6.1")$Package,
|
||||
c("arules", "arulesSequences", "indirect")
|
||||
)
|
||||
})
|
||||
Loading…
Reference in a new issue