Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 642e07e1d6 | |||
| 94e6c697cf | |||
| 213d30cea4 | |||
| 4bf88ed378 | |||
| 448349d075 | |||
| 4413f499f1 | |||
| 0a6c155dca | |||
| 5f901312a6 | |||
| a2923bf063 | |||
| d38a4b5746 |
7 changed files with 226 additions and 22 deletions
|
|
@ -35,6 +35,14 @@ variables:
|
||||||
- 'resolute'
|
- 'resolute'
|
||||||
default: '3.24'
|
default: '3.24'
|
||||||
R_VERSION:
|
R_VERSION:
|
||||||
|
# The slot's *primary* R minor: what `local/build-all.R` builds into the
|
||||||
|
# generic slot. The loop below already runs `--sensitive-only` for every
|
||||||
|
# other installed minor, so filling a non-primary minor's gap needs this
|
||||||
|
# left alone, not changed.
|
||||||
|
#
|
||||||
|
# 4.6.0 was briefly offered here (#183) and removed: selecting it for a
|
||||||
|
# slot whose generic binaries are 4.5-built would publish 4.6 binaries
|
||||||
|
# into the generic slot and break every 4.5 client.
|
||||||
description: 'Primary R version under /opt/R.'
|
description: 'Primary R version under /opt/R.'
|
||||||
options:
|
options:
|
||||||
- 4.5.3
|
- 4.5.3
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,12 @@ steps:
|
||||||
from_secret: BUNNYNET_API_KEY
|
from_secret: BUNNYNET_API_KEY
|
||||||
# cran.rpkgs.com and cran.allianceswisspass.devxy.io are on separate
|
# cran.rpkgs.com and cran.allianceswisspass.devxy.io are on separate
|
||||||
# Bunny pull zones, so both must be purged after the shared origin changes.
|
# Bunny pull zones, so both must be purged after the shared origin changes.
|
||||||
BUNNY_PULLZONES: '3857050 cran.allianceswisspass.devxy.io'
|
# The staging zone is listed too. It shares the B2 origin, so an index
|
||||||
|
# it still holds is a stale copy of the same object, and its
|
||||||
|
# cache_expiration_time is the same ~370 days: without a purge here it
|
||||||
|
# serves pre-reindex indexes indefinitely and any verification run
|
||||||
|
# against it measures the past.
|
||||||
|
BUNNY_PULLZONES: '3857050 cran.allianceswisspass.devxy.io cran-rpkgs-test.b-cdn.net'
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache -q bash curl jq
|
- apk add --no-cache -q bash curl jq
|
||||||
# Crow carries the checkout from the re-index step into this step.
|
# Crow carries the checkout from the re-index step into this step.
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,12 @@ steps:
|
||||||
from_secret: BUNNYNET_API_KEY
|
from_secret: BUNNYNET_API_KEY
|
||||||
# cran.rpkgs.com and cran.allianceswisspass.devxy.io are on separate
|
# cran.rpkgs.com and cran.allianceswisspass.devxy.io are on separate
|
||||||
# Bunny pull zones, so both must be purged after the shared origin changes.
|
# Bunny pull zones, so both must be purged after the shared origin changes.
|
||||||
BUNNY_PULLZONES: '3857050 cran.allianceswisspass.devxy.io'
|
# The staging zone is listed too. It shares the B2 origin, so an index
|
||||||
|
# it still holds is a stale copy of the same object, and its
|
||||||
|
# cache_expiration_time is the same ~370 days: without a purge here it
|
||||||
|
# serves pre-reindex indexes indefinitely and any verification run
|
||||||
|
# against it measures the past.
|
||||||
|
BUNNY_PULLZONES: '3857050 cran.allianceswisspass.devxy.io cran-rpkgs-test.b-cdn.net'
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache -q bash curl jq
|
- apk add --no-cache -q bash curl jq
|
||||||
# Crow carries the checkout from the re-index step into this step.
|
# Crow carries the checkout from the re-index step into this step.
|
||||||
|
|
|
||||||
12
cdn.tf
12
cdn.tf
|
|
@ -91,7 +91,17 @@ resource "bunnynet_compute_script" "rpkgs_router" {
|
||||||
resource "bunnynet_compute_script_variable" "rpkgs_router_union_slots" {
|
resource "bunnynet_compute_script_variable" "rpkgs_router_union_slots" {
|
||||||
script = bunnynet_compute_script.rpkgs_router.id
|
script = bunnynet_compute_script.rpkgs_router.id
|
||||||
name = "UNION_SLOTS"
|
name = "UNION_SLOTS"
|
||||||
default_value = ""
|
# Enabled. Until this was set, every client resolved against the generic
|
||||||
|
# index and never reached a per-minor binary: an R 4.6.1 client on resolute
|
||||||
|
# downloaded the 4.5-built rlang (2079570 bytes) while the correct 4.6 build
|
||||||
|
# (2075106 bytes) sat unused one directory away, and died at load with
|
||||||
|
# `undefined symbol: SETLENGTH`.
|
||||||
|
#
|
||||||
|
# Verified before enabling, against the staging zone with the same script and
|
||||||
|
# the same origin: all 16 slots report zero regressions against the generic
|
||||||
|
# slot, an excluded R minor is sent to CRAN, a client without an R minor
|
||||||
|
# still gets the generic index, and tarball requests are never rewritten.
|
||||||
|
default_value = join(",", local.rpkgs_slots)
|
||||||
required = false
|
required = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,50 @@ package_cache_files <- c(
|
||||||
"/mnt/cache/packages/r_minor_sensitive_pkgs.rds",
|
"/mnt/cache/packages/r_minor_sensitive_pkgs.rds",
|
||||||
"/mnt/cache/packages/s3_cache.rds"
|
"/mnt/cache/packages/s3_cache.rds"
|
||||||
)
|
)
|
||||||
|
# Existence is not freshness. The snapshot describes S3 and CRAN state at the
|
||||||
|
# moment it was written, and the volume is per-agent, so an agent that ran an
|
||||||
|
# earlier pipeline keeps serving that pipeline's answer forever. arm64/alpine324
|
||||||
|
# reported "0 remaining" for both 4.4 and 4.6 from a stale snapshot listing 43
|
||||||
|
# sensitive packages, while the install-deps step in the very same pipeline had
|
||||||
|
# just computed 6871 on another agent.
|
||||||
|
#
|
||||||
|
# Keyed on the pipeline when the CI exposes one, so a new pipeline recomputes
|
||||||
|
# once per agent and its shards then share the result. Off CI, or when no such
|
||||||
|
# variable is set, fall back to an age check.
|
||||||
|
snapshot_id_path <- "/mnt/cache/packages/snapshot.id"
|
||||||
|
snapshot_ttl_hours <- as.numeric(
|
||||||
|
Sys.getenv("PACKAGE_SNAPSHOT_TTL_HOURS", unset = "2")
|
||||||
|
)
|
||||||
|
current_snapshot_id <- ""
|
||||||
|
for (v in c("CI_PIPELINE_NUMBER", "CI_BUILD_NUMBER", "CI_PIPELINE_ID")) {
|
||||||
|
val <- Sys.getenv(v, unset = "")
|
||||||
|
if (nzchar(val)) {
|
||||||
|
current_snapshot_id <- paste(v, val, sep = "=")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot_is_stale <- function() {
|
||||||
if (!all(file.exists(package_cache_files))) {
|
if (!all(file.exists(package_cache_files))) {
|
||||||
|
return(TRUE)
|
||||||
|
}
|
||||||
|
if (nzchar(current_snapshot_id)) {
|
||||||
|
cached <- tryCatch(
|
||||||
|
readLines(snapshot_id_path, warn = FALSE)[1L],
|
||||||
|
error = function(e) NA_character_,
|
||||||
|
warning = function(w) NA_character_
|
||||||
|
)
|
||||||
|
return(!identical(cached, current_snapshot_id))
|
||||||
|
}
|
||||||
|
age_hours <- as.numeric(
|
||||||
|
difftime(Sys.time(), file.mtime(package_cache_files[1L]), units = "hours")
|
||||||
|
)
|
||||||
|
isTRUE(age_hours > snapshot_ttl_hours)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot_is_stale()) {
|
||||||
message(
|
message(
|
||||||
"Package snapshot missing from cache; recomputing via packages-to-build.R"
|
"Package snapshot missing or stale; recomputing via packages-to-build.R"
|
||||||
)
|
)
|
||||||
dir.create("/mnt/cache/packages", showWarnings = FALSE, recursive = TRUE)
|
dir.create("/mnt/cache/packages", showWarnings = FALSE, recursive = TRUE)
|
||||||
save_rds_atomic <- function(obj, path) {
|
save_rds_atomic <- function(obj, path) {
|
||||||
|
|
@ -42,6 +83,9 @@ if (!all(file.exists(package_cache_files))) {
|
||||||
pkgs[r_minor_sensitive == TRUE],
|
pkgs[r_minor_sensitive == TRUE],
|
||||||
"/mnt/cache/packages/r_minor_sensitive_pkgs.rds"
|
"/mnt/cache/packages/r_minor_sensitive_pkgs.rds"
|
||||||
)
|
)
|
||||||
|
if (nzchar(current_snapshot_id)) {
|
||||||
|
writeLines(current_snapshot_id, snapshot_id_path)
|
||||||
|
}
|
||||||
message("Package snapshot recomputed.")
|
message("Package snapshot recomputed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,10 +154,25 @@ con <- DBI::dbConnect(
|
||||||
password = Sys.getenv("PGPASS"),
|
password = Sys.getenv("PGPASS"),
|
||||||
sslmode = "require"
|
sslmode = "require"
|
||||||
)
|
)
|
||||||
|
# Scope the skip to the R minor this pass is running under. `single_builds`
|
||||||
|
# records `r_version` per attempt, but querying without it made a non-primary
|
||||||
|
# pass skip everything the primary pass had already attempted under a different
|
||||||
|
# minor - so `--sensitive-only` under 4.6 skipped packages that had only ever
|
||||||
|
# been built for 4.5, and the per-minor slots never filled. That is why
|
||||||
|
# amd64/resolute served 4000 fewer packages to a 4.6 client than to a 4.5 one.
|
||||||
|
r_minor <- paste(
|
||||||
|
R.version$major,
|
||||||
|
strsplit(R.version$minor, ".", fixed = TRUE)[[1L]][1L],
|
||||||
|
sep = "."
|
||||||
|
)
|
||||||
built <- DBI::dbGetQuery(
|
built <- DBI::dbGetQuery(
|
||||||
con,
|
con,
|
||||||
"SELECT name, tag FROM single_builds WHERE platform = $1 AND arch = $2",
|
paste(
|
||||||
params = list(platform, arch)
|
"SELECT name, tag FROM single_builds",
|
||||||
|
"WHERE platform = $1 AND arch = $2",
|
||||||
|
"AND substring(r_version from '^[0-9]+[.][0-9]+') = $3"
|
||||||
|
),
|
||||||
|
params = list(platform, arch, r_minor)
|
||||||
)
|
)
|
||||||
DBI::dbDisconnect(con)
|
DBI::dbDisconnect(con)
|
||||||
before <- nrow(chunk)
|
before <- nrow(chunk)
|
||||||
|
|
@ -121,8 +180,9 @@ chunk <- chunk[
|
||||||
!paste(chunk$Package, chunk$Version) %in% paste(built$name, built$tag),
|
!paste(chunk$Package, chunk$Version) %in% paste(built$name, built$tag),
|
||||||
]
|
]
|
||||||
sprintf(
|
sprintf(
|
||||||
"Skipped %d already-attempted package versions; %d remaining for this job",
|
"Skipped %d package versions already attempted under R %s; %d remaining for this job",
|
||||||
before - nrow(chunk),
|
before - nrow(chunk),
|
||||||
|
r_minor,
|
||||||
nrow(chunk)
|
nrow(chunk)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,36 @@ archive_versions <- archive_versions[
|
||||||
]
|
]
|
||||||
|
|
||||||
# Now get release versions (assuming cran_release has Package and Version columns)
|
# Now get release versions (assuming cran_release has Package and Version columns)
|
||||||
|
#
|
||||||
|
# Packages published in the last few days are held back. `check_for_binary()`
|
||||||
|
# reads the published version from the `cran` GitHub mirror
|
||||||
|
# (`GET /repos/cran/<pkg>/commits`), and that mirror lags CRAN: a package that
|
||||||
|
# has just appeared has no repository there yet. The call then 404s, which is
|
||||||
|
# permanent, but it is wrapped in `purrr::insistently` and retried ten times
|
||||||
|
# with a backoff capped at 60s - so one unmirrored package burns about five
|
||||||
|
# minutes and then aborts the whole shard.
|
||||||
|
#
|
||||||
|
# Holding them back costs nothing: the daily update pipeline builds new and
|
||||||
|
# updated packages anyway, and they arrive here on the next run once the mirror
|
||||||
|
# has caught up.
|
||||||
|
mirror_lag_days <- as.numeric(
|
||||||
|
Sys.getenv("CRAN_MIRROR_LAG_DAYS", unset = "3")
|
||||||
|
)
|
||||||
|
published <- as.POSIXct(cran_release$Published, tz = "UTC")
|
||||||
|
too_recent <- !is.na(published) &
|
||||||
|
published > (Sys.time() - mirror_lag_days * 86400)
|
||||||
|
if (any(too_recent)) {
|
||||||
|
message(sprintf(
|
||||||
|
"Holding back %d package(s) published in the last %g day(s); the cran GitHub mirror will not have them yet: %s",
|
||||||
|
sum(too_recent),
|
||||||
|
mirror_lag_days,
|
||||||
|
paste(utils::head(cran_release$Package[too_recent], 10L), collapse = ", ")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
release_versions <- data.table(
|
release_versions <- data.table(
|
||||||
Package = cran_release$Package,
|
Package = cran_release$Package[!too_recent],
|
||||||
Version = as.character(cran_release$Version)
|
Version = as.character(cran_release$Version[!too_recent])
|
||||||
)
|
)
|
||||||
|
|
||||||
pkgs_to_build <- unique(rbind(archive_versions, release_versions, fill = TRUE))
|
pkgs_to_build <- unique(rbind(archive_versions, release_versions, fill = TRUE))
|
||||||
|
|
@ -89,7 +116,28 @@ s3_pkgs <- s3fs::s3_dir_ls(
|
||||||
recurse = TRUE
|
recurse = TRUE
|
||||||
)
|
)
|
||||||
|
|
||||||
file_names <- basename(s3_pkgs)
|
# `recurse = TRUE` walks the per-minor slots as well, and `basename()` throws
|
||||||
|
# the directory away - so `4.5/curl_1.0.tar.gz` and `curl_1.0.tar.gz` collapse
|
||||||
|
# to one name and a package present under *any* R minor counts as built for
|
||||||
|
# *all* of them. The candidate list then prunes exactly the packages a
|
||||||
|
# per-minor pass exists to build: arm64/alpine324 reported "0 remaining" for
|
||||||
|
# both 4.4 and 4.6 while its indexes were dropping 2400+ packages as missing.
|
||||||
|
#
|
||||||
|
# Per-minor objects are therefore excluded here. Presence in a specific minor
|
||||||
|
# is decided downstream, where the running R version is known: build-all.R
|
||||||
|
# filters on it, and `build_binary_package()` checks the per-minor path per
|
||||||
|
# package and skips what is already there.
|
||||||
|
#
|
||||||
|
# Archive/ is kept. Those are versions that were built and then superseded;
|
||||||
|
# dropping them would make every archived version look unbuilt.
|
||||||
|
per_minor_object <- grepl("/[0-9]+\\.[0-9]+/[^/]+$", s3_pkgs)
|
||||||
|
if (any(per_minor_object)) {
|
||||||
|
cat(sprintf(
|
||||||
|
"Excluding %d per-minor object(s) from the presence check; those are decided per pass\n",
|
||||||
|
sum(per_minor_object)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
file_names <- basename(s3_pkgs[!per_minor_object])
|
||||||
|
|
||||||
# An object occupying a key is not proof a binary was built: a package whose
|
# An object occupying a key is not proof a binary was built: a package whose
|
||||||
# build failed has its CRAN source published under exactly that name. Left in
|
# build failed has its CRAN source published under exactly that name. Left in
|
||||||
|
|
@ -152,11 +200,22 @@ s3_dt <- data.table(
|
||||||
|
|
||||||
### Get all packages with build errors
|
### Get all packages with build errors
|
||||||
|
|
||||||
|
# Scoped to the R minor this snapshot is computed under. A failure is a fact
|
||||||
|
# about one interpreter: without the scope a package that failed under the
|
||||||
|
# primary minor is dropped from the candidate list for every other minor too,
|
||||||
|
# which is the same omission fixed in local/build-all.R and in bincraft's
|
||||||
|
# check_package_error().
|
||||||
|
snapshot_r_minor <- paste(
|
||||||
|
R.version$major,
|
||||||
|
strsplit(R.version$minor, ".", fixed = TRUE)[[1L]][1L],
|
||||||
|
sep = "."
|
||||||
|
)
|
||||||
sql_query <- paste0(
|
sql_query <- paste0(
|
||||||
# nolint
|
# nolint
|
||||||
"SELECT error_occurred FROM ",
|
"SELECT error_occurred FROM ",
|
||||||
"single_builds",
|
"single_builds",
|
||||||
" WHERE name = $1 AND tag = $2 AND platform = $3 AND arch = $4"
|
" WHERE name = $1 AND tag = $2 AND platform = $3 AND arch = $4",
|
||||||
|
" AND substring(r_version from '^[0-9]+[.][0-9]+') = $5"
|
||||||
)
|
)
|
||||||
# Function to query for a single package-version
|
# Function to query for a single package-version
|
||||||
query_error <- function(pkg, ver) {
|
query_error <- function(pkg, ver) {
|
||||||
|
|
@ -164,7 +223,7 @@ query_error <- function(pkg, ver) {
|
||||||
~ DBI::dbGetQuery(
|
~ DBI::dbGetQuery(
|
||||||
con,
|
con,
|
||||||
sql_query,
|
sql_query,
|
||||||
params = list(pkg, ver, platform, arch)
|
params = list(pkg, ver, platform, arch, snapshot_r_minor)
|
||||||
),
|
),
|
||||||
rate = purrr::rate_backoff(
|
rate = purrr::rate_backoff(
|
||||||
pause_base = 1L,
|
pause_base = 1L,
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,13 @@ fetch() {
|
||||||
local status
|
local status
|
||||||
# -L: the router answers an index request with a redirect, so the bytes a
|
# -L: the router answers an index request with a redirect, so the bytes a
|
||||||
# client ends up with are only visible by following it.
|
# client ends up with are only visible by following it.
|
||||||
|
#
|
||||||
|
# no-cache: a purge is asynchronous, so a run started right after a reindex
|
||||||
|
# otherwise measures whatever the edge still holds.
|
||||||
if [ -n "$ua" ]; then
|
if [ -n "$ua" ]; then
|
||||||
status=$(curl -sSL -A "$ua" -o "$dest" -w '%{http_code}' --max-time 120 "$url" 2>/dev/null)
|
status=$(curl -sSL -A "$ua" -H 'Cache-Control: no-cache' -o "$dest" -w '%{http_code}' --max-time 120 "$url" 2>/dev/null)
|
||||||
else
|
else
|
||||||
status=$(curl -sSL -o "$dest" -w '%{http_code}' --max-time 120 "$url" 2>/dev/null)
|
status=$(curl -sSL -H 'Cache-Control: no-cache' -o "$dest" -w '%{http_code}' --max-time 120 "$url" 2>/dev/null)
|
||||||
fi
|
fi
|
||||||
echo "$status" > "$dest.status"
|
echo "$status" > "$dest.status"
|
||||||
echo "$status"
|
echo "$status"
|
||||||
|
|
@ -137,6 +140,23 @@ fallback_counts() {
|
||||||
'
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Packages this index serves from the generic slot with a binary built under a
|
||||||
|
# different R minor, while some other per-minor slot carries a build of them -
|
||||||
|
# which proves the ABI classifier called them risky. Serving those is the
|
||||||
|
# load-time crash the per-minor slots exist to prevent. bincraft drops them at
|
||||||
|
# index time, so a non-zero count means the slot has not been reindexed since
|
||||||
|
# that guard shipped.
|
||||||
|
abi_unsafe_count() {
|
||||||
|
local minor_file=$1 minor=$2 risky_file=$3
|
||||||
|
gunzip -c "$minor_file" 2>/dev/null | awk -v m="$minor" '
|
||||||
|
/^Package:/ { pkg = $2; path = ""; built = "" }
|
||||||
|
/^Path:/ { path = $2 }
|
||||||
|
/^Built:/ { built = $2 " " $3 }
|
||||||
|
/^$/ { if (pkg != "" && path == "" && built != "" && built !~ ("^R " m "\\.")) print pkg; pkg = "" }
|
||||||
|
' | sort -u > "$minor_file.mismatched"
|
||||||
|
comm -12 "$minor_file.mismatched" "$risky_file" | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
# How many packages a client of <minor> would receive as source through
|
# How many packages a client of <minor> would receive as source through
|
||||||
# per-minor routing while the generic slot holds a binary built under that very
|
# per-minor routing while the generic slot holds a binary built under that very
|
||||||
# minor. Zero is the bar for enabling a slot.
|
# minor. Zero is the bar for enabling a slot.
|
||||||
|
|
@ -216,12 +236,25 @@ for arch in $ARCHES; do
|
||||||
minor_count=$(wc -l < "$minor_file.names")
|
minor_count=$(wc -l < "$minor_file.names")
|
||||||
|
|
||||||
# Union property: nothing the flat index carries may be missing here.
|
# Union property: nothing the flat index carries may be missing here.
|
||||||
missing=$(comm -23 "$flat_file.names" "$minor_file.names" | head -5)
|
# bincraft deliberately drops an ABI-risky package whose only binary was
|
||||||
missing_count=$(comm -23 "$flat_file.names" "$minor_file.names" | wc -l)
|
# built under another R minor: serving it is the load-time crash the
|
||||||
if [ "$missing_count" -ne 0 ]; then
|
# per-minor slots exist to prevent. Those absences are correct.
|
||||||
bad "$slot R $minor index is not a union: $missing_count flat packages absent (e.g. $(echo "$missing" | tr '\n' ' '))"
|
#
|
||||||
|
# What must never go missing is a generic package built under *this*
|
||||||
|
# minor, which is safe to serve and has no reason to disappear.
|
||||||
|
comm -23 "$flat_file.names" "$minor_file.names" > "$minor_file.absent"
|
||||||
|
absent_count=$(wc -l < "$minor_file.absent")
|
||||||
|
gunzip -c "$flat_file" 2>/dev/null | awk -v m="$minor" '
|
||||||
|
/^Package:/ { pkg = $2; built = "" }
|
||||||
|
/^Built:/ { built = $2 " " $3 }
|
||||||
|
/^$/ { if (pkg != "" && built ~ ("^R " m "\\.")) print pkg; pkg = "" }
|
||||||
|
' | sort -u > "$minor_file.flatsame"
|
||||||
|
lost=$(comm -12 "$minor_file.absent" "$minor_file.flatsame" | wc -l)
|
||||||
|
|
||||||
|
if [ "${lost:-0}" -ne 0 ]; then
|
||||||
|
bad "$slot R $minor index dropped $lost generic package(s) built under R $minor, which were safe to serve"
|
||||||
else
|
else
|
||||||
ok "$slot R $minor index: $minor_count packages, union holds"
|
ok "$slot R $minor index: $minor_count packages, union holds ($absent_count ABI-unsafe dropped)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# A per-minor entry that is a source fallback resolves fine but makes the
|
# A per-minor entry that is a source fallback resolves fine but makes the
|
||||||
|
|
@ -311,6 +344,30 @@ for arch in $ARCHES; do
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Packages carrying a Path in any per-minor index are risky by construction.
|
||||||
|
: > "$WORK/${arch}-${distro}.risky"
|
||||||
|
for minor in $MINORS; do
|
||||||
|
f="$WORK/${arch}-${distro}-${minor}.gz"
|
||||||
|
[ -s "$f" ] || continue
|
||||||
|
gunzip -c "$f" 2>/dev/null | awk '
|
||||||
|
/^Package:/ { pkg = $2; path = "" }
|
||||||
|
/^Path:/ { path = $2 }
|
||||||
|
/^$/ { if (pkg != "" && path != "") print pkg; pkg = "" }
|
||||||
|
' >> "$WORK/${arch}-${distro}.risky"
|
||||||
|
done
|
||||||
|
sort -u -o "$WORK/${arch}-${distro}.risky" "$WORK/${arch}-${distro}.risky"
|
||||||
|
|
||||||
|
for minor in $MINORS; do
|
||||||
|
f="$WORK/${arch}-${distro}-${minor}.gz"
|
||||||
|
[ -s "$f" ] || continue
|
||||||
|
unsafe=$(abi_unsafe_count "$f" "$minor" "$WORK/${arch}-${distro}.risky")
|
||||||
|
if [ "${unsafe:-0}" -gt 0 ]; then
|
||||||
|
bad "$slot R $minor serves $unsafe ABI-risky package(s) built under another R minor - reindex this slot"
|
||||||
|
else
|
||||||
|
ok "$slot R $minor serves no ABI-risky package from another minor"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# Excluded minors: no published index, and under --live a redirect to CRAN.
|
# Excluded minors: no published index, and under --live a redirect to CRAN.
|
||||||
for minor in $EXCLUDED_MINORS; do
|
for minor in $EXCLUDED_MINORS; do
|
||||||
ex_url="$BASE/$slot/latest/src/contrib/$minor/PACKAGES.gz"
|
ex_url="$BASE/$slot/latest/src/contrib/$minor/PACKAGES.gz"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue