build-cran-binaries/local/trial-build-registry.R
pat-s 118a92889f
Some checks failed
ci/crow/cron/process-updates/2 Pipeline was successful
ci/crow/cron/process-updates/7 Pipeline was successful
ci/crow/cron/process-updates/8 Pipeline was successful
ci/crow/cron/process-updates/9 Pipeline was successful
ci/crow/cron/process-updates/3 Pipeline was successful
ci/crow/cron/process-updates/4 Pipeline was successful
ci/crow/cron/process-updates/10 Pipeline was successful
ci/crow/cron/process-updates/13 Pipeline was successful
ci/crow/manual/build-all-versions-install-deps/2 Pipeline was successful
ci/crow/cron/process-updates/14 Pipeline was successful
ci/crow/cron/process-updates/15 Pipeline was successful
ci/crow/cron/process-updates/16 Pipeline was successful
ci/crow/cron/process-updates/11 Pipeline was successful
ci/crow/cron/process-updates/18 Pipeline was successful
ci/crow/cron/process-updates/17 Pipeline was successful
ci/crow/cron/process-updates/5 Pipeline was successful
ci/crow/cron/process-updates/12 Pipeline was successful
ci/crow/manual/auto-apply-patches Pipeline was successful
ci/crow/cron/process-updates/6 Pipeline was successful
ci/crow/cron/process-updates/1 Pipeline was successful
ci/crow/manual/build-all-versions/7 Pipeline failed
ci/crow/manual/build-all-versions/8 Pipeline failed
ci/crow/manual/build-all-versions/5 Pipeline failed
ci/crow/manual/build-all-versions/6 Pipeline failed
fix(local): make the trial-build gate detect non-throwing build failures (#130)
## Critical: the gate was false-green

The latest run printed **`3/3 passed on alpine-324`** while all three builds actually **failed** (their `rstan` dependency won't compile). A false-green gate would let broken registry entries merge — worse than no gate.

Root cause: `bincraft::build_binary_package()` catches build failures internally and **returns `"error"`** for the failed tag rather than throwing. The gate's `tryCatch` only treated a *thrown* exception as failure, so every non-throwing failure looked like a pass.

Fix: inspect the return value. A tag passes only if the flattened result is non-empty and contains no `"error"` sentinel; a thrown error still counts as failure. Verified the verdict against `error`/`skipped`/`TRUE`/`list(success=TRUE)`/`NULL`/mixed inputs.

With this, the current rstan-blocked entries will correctly show **0/3 (red)** — which is the right answer until rstan builds.

Reviewed-on: #130
2026-07-16 08:24:03 +00:00

163 lines
4.6 KiB
R

#!/usr/bin/env Rscript
# Merge gate for the auto-patch PR (issue #115, step 3): for every registry
# entry the PR ADDS that applies to this platform, trial-build the package with
# the registry applied, in this platform's own build-env image. Nothing is
# uploaded, archived, or written to the metadata DB.
#
# Exit 0 only if every new entry's package builds; exit 1 if any fails, so it
# gates the PR. A platform with no new entries is a fast no-op.
#
# Usage (inside a build-env image):
# PLATFORM=ubuntu-2604 Rscript local/trial-build-registry.R [base_ref]
# base_ref git ref to diff the registry against (default: origin/main)
options(error = function() {
cat("ERROR:", geterrmessage(), "\n", file = stdout())
q(status = 1)
})
suppressPackageStartupMessages({
library(jsonlite, quietly = TRUE)
library(bincraft, quietly = TRUE)
})
script_path <- local({
a <- commandArgs(trailingOnly = FALSE)
f <- sub("^--file=", "", a[grepl("^--file=", a)])
if (length(f) == 1L && nzchar(f)) normalizePath(f) else NA_character_
})
script_dir <- if (is.na(script_path)) "local" else dirname(script_path)
source(file.path(script_dir, "proposal-tracking-lib.R"))
args <- commandArgs(trailingOnly = TRUE)
base_ref <- if (length(args) >= 1L) {
args[[1L]]
} else {
Sys.getenv("BASE_REF", "origin/main")
}
os <- Sys.getenv("PLATFORM", "")
if (!nzchar(os)) {
stop("PLATFORM env var is not set (e.g. ubuntu-2604).")
}
patches_dir <- file.path(script_dir, "patches")
registry_file <- file.path(patches_dir, "registry.json")
current <- if (file.exists(registry_file)) {
jsonlite::fromJSON(registry_file, simplifyVector = FALSE)
} else {
list()
}
# Read the registry at base_ref. Fail loud if the ref or file can't be read:
# silently treating the base as empty would trial-build the WHOLE registry
# instead of just the entries the branch adds.
registry_rel <- "local/patches/registry.json"
# system2() with captured output runs via /bin/sh, so shell-quote the git args
# (refs contain "^{}" and ":" that the shell would otherwise mangle).
ref_ok <- suppressWarnings(system2(
"git",
shQuote(c(
"rev-parse",
"--verify",
"--quiet",
sprintf("%s^{commit}", base_ref)
)),
stdout = TRUE,
stderr = FALSE
))
if (!is.null(attr(ref_ok, "status"))) {
stop(sprintf("base ref %s does not resolve to a commit.", base_ref))
}
in_base <- suppressWarnings(system2(
"git",
shQuote(c("ls-tree", base_ref, "--", registry_rel)),
stdout = TRUE,
stderr = FALSE
))
file_in_base <- length(in_base) > 0L && any(nzchar(in_base))
base_json <- suppressWarnings(system2(
"git",
shQuote(c("show", sprintf("%s:%s", base_ref, registry_rel))),
stdout = TRUE,
stderr = FALSE
))
show_ok <- is.null(attr(base_json, "status"))
if (file_in_base && !show_ok) {
stop(sprintf(
"could not read %s at %s; refusing to build the whole registry.",
registry_rel,
base_ref
))
}
base <- if (show_ok && length(base_json) > 0L) {
jsonlite::fromJSON(paste(base_json, collapse = "\n"), simplifyVector = FALSE)
} else {
list() # file genuinely absent at base -> every entry is new
}
pkgs <- new_registry_packages(current, base, os = os)
if (length(pkgs) == 0L) {
cat(sprintf(
"No new registry entries apply to %s; nothing to trial-build.\n",
os
))
q(status = 0)
}
cat(sprintf(
"Trial-building %d new registry %s on %s (vs %s):\n %s\n",
length(pkgs),
if (length(pkgs) == 1L) "entry" else "entries",
os,
base_ref,
toString(pkgs)
))
results <- vapply(
pkgs,
function(pkg) {
cat(sprintf("\n=== trial build: %s ===\n", pkg))
# bincraft::build_binary_package() catches build failures internally and
# RETURNS "error" for the failed tag rather than throwing, so a green gate
# must inspect the return value -- checking only for a thrown exception
# reports a broken build as passing.
res <- tryCatch(
bincraft::build_binary_package(
pkg,
tag_limit = 1L,
patches = patches_dir,
archive = FALSE,
upload = FALSE,
store_build_metadata = FALSE
),
error = function(e) {
cat(sprintf("FAILED %s (threw): %s\n", pkg, conditionMessage(e)))
"error"
}
)
flat <- as.character(unlist(res))
ok <- length(flat) > 0L && !("error" %in% flat)
if (!ok) {
cat(sprintf(
"FAILED %s: build did not succeed (result: %s)\n",
pkg,
if (length(flat) > 0L) toString(flat) else "<empty>"
))
}
ok
},
logical(1L)
)
failed <- pkgs[!results]
cat(sprintf(
"\n%d/%d passed on %s.%s\n",
sum(results),
length(results),
os,
if (length(failed) > 0L) sprintf(" Failed: %s", toString(failed)) else ""
))
if (length(failed) > 0L) {
q(status = 1)
}
q(status = 0)