build-cran-binaries/local/uvr-install.sh
pat-s aa4c95457f
All checks were successful
ci/crow/cron/process-updates/7 Pipeline was successful
ci/crow/manual/weekly-rebuild-missing/54 Pipeline was successful
ci/crow/manual/weekly-rebuild-missing/52 Pipeline was successful
ci/crow/manual/weekly-rebuild-missing/53 Pipeline was successful
ci/crow/manual/weekly-rebuild-reindex/18 Pipeline was successful
fix(rebuild): harden split workflow setup (#164)
## Motivation

Weekly rebuild shards can all hit a transient CRAN DNS/index outage at once, and the dependent CDN purge always fails because it tries to clone over the checkout preserved from the re-index step.

## Changes

- Retry `uvr add` resolution up to four times with bounded backoff.
- Reuse the existing Crow workspace checkout in the CDN purge step.
- Remove the purge step's unused Git package and repository token.

## Validation

- `crow lint .crow/`
- `shellcheck local/uvr-install.sh scripts/purge_cdn_zone.sh`
- `git diff --check`

Reviewed-on: #164
2026-08-13 13:38:28 +00:00

120 lines
5 KiB
Shell
Executable file

#!/bin/sh
# Install R packages into the CI library with uvr (https://github.com/nbafrank/uvr).
#
# Usage:
# local/uvr-install.sh httr2 jsonlite
# local/uvr-install.sh forgejo::codefloe.com/rpkgs/bincraft@v4.4.3
#
# Replaces `pak::pak(...)`. uvr is project-scoped: `uvr add` refuses to run
# outside a project and always writes to `.uvr/library/`, and only
# `uvr sync --library` can target an existing library. The project is therefore
# minted in a scratch directory under TMPDIR and thrown away afterwards; that
# also keeps `uvr init`'s `.Rprofile` out of the repo checkout, where it would
# hijack `.libPaths()` for every other R call in the pipeline.
#
# Pruning is a no-op here: uvr disables it whenever `--library` is passed,
# precisely because such a target may be shared (`/mnt/cache/R-pkgs` holds
# bincraft and its dependencies alongside whatever this script installs).
#
# System dependencies come from uvr's vendored r-system-requirements rules, so
# `pak::sysreqs_db_update()` and `PKG_SYSREQS_PLATFORM` are no longer needed.
#
# Environment:
# UVR_R_BIN R interpreter to install for; set by install-bincraft.R so
# the per-R-minor passes target their own R, not the primary
# R_VERSION fallback interpreter selector (/opt/R/<version>/bin/R)
# UVR_TARGET_LIB target library; defaults to R_LIBS_USER, then to the
# active R's .libPaths()[1] (which is where pak wrote)
# UVR_INSTALL_DIR where the uvr binary lands (default /usr/local/bin)
set -eu
# renovate: datasource=github-releases depName=nbafrank/uvr
UVR_PIN="v0.4.6"
if [ "$#" -eq 0 ]; then
echo "usage: $0 <pkg-spec>..." >&2
exit 2
fi
# The build images keep R under /opt/R/<version> and off PATH. uvr resolves the
# interpreter via PATH and never downloads one unless `uvr r install` is run, so
# put the requested R first.
r_bin="${UVR_R_BIN:-}"
if [ -z "$r_bin" ] && [ -n "${R_VERSION:-}" ] && [ -x "/opt/R/${R_VERSION}/bin/R" ]; then
r_bin="/opt/R/${R_VERSION}/bin/R"
fi
if [ -n "$r_bin" ]; then
PATH="$(dirname "$r_bin"):$PATH"
export PATH
else
r_bin="$(command -v R)"
fi
target_lib="${UVR_TARGET_LIB:-${R_LIBS_USER:-}}"
if [ -z "$target_lib" ]; then
target_lib="$("$r_bin" --no-echo --no-save -e 'cat(.libPaths()[1])')"
fi
if [ -z "$target_lib" ]; then
echo "error: could not determine a target library; set UVR_TARGET_LIB" >&2
exit 2
fi
mkdir -p "$target_lib"
# Pin the manifest to the active R so the lockfile's R stays in step with the
# library's R sentinel. Without that, uvr can decide the library is ABI-stale
# and wipe it -- and this target is shared with bincraft. uvr only discovers R
# via PATH/R_HOME (it does not scan /opt/R), so the R put on PATH above is the
# only candidate this constraint can resolve to.
# shellcheck disable=SC2016 # $major/$minor are R expressions, not shell vars
r_full="$("$r_bin" --no-echo --no-save -e 'cat(paste(R.version$major, R.version$minor, sep = "."))')"
install_dir="${UVR_INSTALL_DIR:-/usr/local/bin}"
uvr_bin="${install_dir}/uvr"
if [ ! -x "$uvr_bin" ]; then
echo "Bootstrapping uvr ${UVR_PIN} into ${install_dir}"
UVR_INSTALL_DIR="$install_dir" UVR_VERSION="$UVR_PIN" \
sh -c 'curl -fsSL https://raw.githubusercontent.com/nbafrank/uvr/main/install.sh | sh'
fi
project_dir="${TMPDIR:-/tmp}/uvr-ci-$$"
rm -rf "$project_dir"
mkdir -p "$project_dir"
trap 'rm -rf "$project_dir"' EXIT
cd "$project_dir"
"$uvr_bin" init --here --r-version "$r_full"
# --no-install resolves and locks only; retry because concurrent shards can
# expose short-lived DNS or CRAN-index failures and uvr rolls the manifest back
# cleanly after an unsuccessful resolution.
add_attempt=1
while ! "$uvr_bin" add --no-install "$@"; do
if [ "$add_attempt" -ge 4 ]; then
echo "error: uvr add failed after ${add_attempt} attempts" >&2
exit 1
fi
add_delay=$((add_attempt * 10))
echo "warning: uvr add attempt ${add_attempt} failed; retrying in ${add_delay}s" >&2
sleep "$add_delay"
add_attempt=$((add_attempt + 1))
done
# TEMPORARY (drop once the images ship a uvr above v0.4.5): the sync below runs
# `apt-get install` for every resolved system dependency without refreshing the
# index first, and the ubuntu build images end their apt layers with
# `rm -rf /var/lib/apt/lists/*`. With no index apt cannot resolve a package that
# exists and is enabled, so `igraph needs: libglpk-dev` fails the whole build
# with `E: Unable to locate package libglpk-dev` on ubuntu 26.04.
#
# Fixed upstream in `e491b2e`, tagged one day after v0.4.5 (nbafrank/uvr#250),
# and the images pick it up via build-env-images#23 — but only after an image
# rebuild is triggered, which is why this runs here too.
#
# apt only: `apk add` fetches its index implicitly and dnf refreshes expired
# metadata on its own. Non-fatal, since a refresh failure still leaves whatever
# index is already there, and the install's own error is the more actionable one.
if command -v apt-get >/dev/null 2>&1; then
apt-get update -qq || echo "warning: apt-get update failed; continuing" >&2
fi
"$uvr_bin" sync --library "$target_lib" --install-system-deps