diff --git a/.crow/auto-apply-patches.yaml b/.crow/auto-apply-patches.yaml deleted file mode 100644 index 2ac6886..0000000 --- a/.crow/auto-apply-patches.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# Auto-apply registry patches (issue #115, step 3 automation). -# Classifies `single_builds` failures and, for the top-N auto-proposable -# candidates by failure volume, writes the registry entries onto the reused -# `auto/registry-patch-proposals` branch and opens/updates a single PR. -# Nothing merges: the `trial-build-registry` pipeline is the merge gate, and a -# human reviews the PR. Novel source diffs / unknown signatures are never -# proposed. Global across platforms, so a single job -- no matrix. -# -# FORGEJO_TOKEN is used for both the branch push and opening the PR (no separate -# write-scoped secret needed). Register the `auto-apply-patches` cron in the crow -# UI, or run manually: -# woodpecker-cli pipeline create --var task=auto-apply-patches --branch=main 7 -variables: - patch_limit: - description: 'Max candidates to propose per run (top by failure volume).' - default: '10' - -when: - - event: manual - evaluate: 'task == "auto-apply-patches"' - - event: cron - cron: auto-apply-patches - -skip_clone: true - -labels: - group: rpkgs-amd64 - -steps: - - name: 'Auto-apply registry patches' - image: reg.devxy.io/rpkgs/build-env-alpine:3.24 - pull: true - environment: - PGPASS: - from_secret: PGPASS - REPO_RO_TOKEN: - from_secret: REPO_RO_TOKEN - FORGEJO_TOKEN: - from_secret: FORGEJO_TOKEN - GIT_USER: devxy-bot - GIT_EMAIL: bot@devxy.io - PATCH_LIMIT: ${patch_limit} - R_VERSION: 4.5.3 - R_LIBS_USER: /mnt/cache/R-pkgs - commands: - - git clone -q https://pat-s:$$REPO_RO_TOKEN@git.devxy.io/devxy/build-cran-binaries.git . - - mkdir -p /mnt/cache/R-pkgs - - rm -rf /mnt/cache/R-pkgs/00LOCK-* - - /opt/R/$R_VERSION/bin/R -q -e 'pak::pak(c("httr2", "jsonlite"))' - - /opt/R/$R_VERSION/bin/Rscript local/propose-patches.R --open-pr --limit $PATCH_LIMIT - backend_options: - kubernetes: - resources: - requests: - memory: 1Gi - cpu: 2000m - limits: - memory: 2Gi - cpu: 2000m - tolerations: - - key: 'CI' - operator: 'Equal' - value: 'true' - effect: 'NoSchedule' diff --git a/.crow/trial-build-registry.yaml b/.crow/trial-build-registry.yaml deleted file mode 100644 index 1ac71e8..0000000 --- a/.crow/trial-build-registry.yaml +++ /dev/null @@ -1,140 +0,0 @@ -# Merge gate for the auto-patch PR (issue #115, step 3). -# For each platform, trial-builds every registry entry the auto-patch branch -# ADDS (vs main) in that platform's own `reg.devxy.io/rpkgs/build-env-*` image, -# with the registry applied. A row with no new entries for its platform is a -# fast no-op. The pipeline is green only if every new entry builds, so it gates -# the PR before merge. Nothing is uploaded/archived/recorded. -# -# The repo uses no `pull_request` triggers, so this runs manually against the -# branch (or on a cron); point it at the auto-patch branch via `patch_branch`: -# woodpecker-cli pipeline create --var task=trial-build-registry \ -# --var patch_branch=auto/registry-patch-proposals --branch=main 7 -variables: - patch_branch: - description: 'Branch whose new registry entries to trial-build.' - default: auto/registry-patch-proposals - -when: - - event: manual - evaluate: 'task == "trial-build-registry"' - - event: cron - cron: trial-build-registry - -skip_clone: true - -labels: - group: rpkgs-${ARCH} - -matrix: - include: - - OS: alpine-322 - ARCH: amd64 - R_VERSION: 4.5.3 - IMG: alpine:3.24 - - OS: alpine-322 - ARCH: arm64 - R_VERSION: 4.5.3 - IMG: alpine:3.24 - - OS: alpine-323 - ARCH: amd64 - R_VERSION: 4.5.3 - IMG: alpine:3.24 - - OS: alpine-323 - ARCH: arm64 - R_VERSION: 4.5.3 - IMG: alpine:3.24 - - OS: alpine-324 - ARCH: amd64 - R_VERSION: 4.5.3 - IMG: alpine:3.24 - - OS: alpine-324 - ARCH: arm64 - R_VERSION: 4.5.3 - IMG: alpine:3.24 - - OS: redhat-8 - ARCH: amd64 - R_VERSION: 4.4.3 - IMG: redhat:8 - - OS: redhat-8 - ARCH: arm64 - R_VERSION: 4.4.3 - IMG: redhat:8 - - OS: redhat-9 - ARCH: amd64 - R_VERSION: 4.4.3 - IMG: redhat:9 - - OS: redhat-9 - ARCH: arm64 - R_VERSION: 4.4.3 - IMG: redhat:9 - - OS: redhat-10 - ARCH: amd64 - R_VERSION: 4.5.3 - IMG: redhat:10 - - OS: redhat-10 - ARCH: arm64 - R_VERSION: 4.5.3 - IMG: redhat:10 - - OS: ubuntu-2204 - ARCH: amd64 - R_VERSION: 4.4.3 - IMG: ubuntu:jammy - - OS: ubuntu-2204 - ARCH: arm64 - R_VERSION: 4.4.3 - IMG: ubuntu:jammy - - OS: ubuntu-2404 - ARCH: amd64 - R_VERSION: 4.4.3 - IMG: ubuntu:noble - - OS: ubuntu-2404 - ARCH: arm64 - R_VERSION: 4.4.3 - IMG: ubuntu:noble - - OS: ubuntu-2604 - ARCH: amd64 - R_VERSION: 4.5.3 - IMG: ubuntu:resolute - - OS: ubuntu-2604 - ARCH: arm64 - R_VERSION: 4.5.3 - IMG: ubuntu:resolute - -steps: - - name: 'Trial-build new registry entries' - image: reg.devxy.io/rpkgs/build-env-${IMG} - pull: true - environment: - B2_S3_ACCESS_KEY: - from_secret: B2_S3_ACCESS_KEY - B2_S3_SECRET_KEY: - from_secret: B2_S3_SECRET_KEY - REPO_RO_TOKEN: - from_secret: REPO_RO_TOKEN - GITHUB_PAT: - from_secret: GITHUB_PAT - PLATFORM: ${OS} - ARCH: ${ARCH} - R_VERSION: ${R_VERSION} - R_LIBS_USER: /mnt/cache/R-pkgs - commands: - - git clone -q --branch ${patch_branch} https://pat-s:$$REPO_RO_TOKEN@git.devxy.io/devxy/build-cran-binaries.git . - - git fetch -q origin main - - mkdir -p /mnt/cache/R-pkgs - - rm -rf /mnt/cache/R-pkgs/00LOCK-* - - /opt/R/$R_VERSION/bin/Rscript local/install-bincraft.R - - /opt/R/$R_VERSION/bin/Rscript local/trial-build-registry.R origin/main - backend_options: - kubernetes: - resources: - requests: - memory: 2Gi - cpu: 2000m - limits: - memory: 4Gi - cpu: 2000m - tolerations: - - key: 'CI' - operator: 'Equal' - value: 'true' - effect: 'NoSchedule' diff --git a/local/patches/README.md b/local/patches/README.md index ce60c9a..992395f 100644 --- a/local/patches/README.md +++ b/local/patches/README.md @@ -81,20 +81,6 @@ Run the trial build inside the failing platform's build-env image; it uploads/ar Rscript local/trial-build-patch.R ``` -#### Autonomous PR + trial-build gate - -`--open-pr` closes the loop: it writes the top-N candidates (by failure volume) onto the reused `auto/registry-patch-proposals` branch and opens/updates a single PR. -`.crow/auto-apply-patches.yaml` runs this on a cron. -`FORGEJO_TOKEN` is used for both the branch push and the PR (no separate write-scoped secret). - -```bash -# Bounded batch; opens/updates one PR. -PGPASS=... FORGEJO_TOKEN=... Rscript local/propose-patches.R --open-pr --limit 10 -``` - -The merge gate is `.crow/trial-build-registry.yaml`: matrixed over the build-env images, each platform trial-builds only the entries the branch **adds** (`local/trial-build-registry.R`, which diffs the registry against `main`) and is green only if every new entry builds. -The repo uses no `pull_request` triggers, so this gate runs manually or on a cron against the auto-patch branch (`--var patch_branch=...`); wiring it to fire on the PR needs `event: pull_request` enabled on the forge. - `--write` and `--open-issue` also append to `local/patches/proposals-log.json`, a ledger of what was proposed. ### Feedback loop (step 4) diff --git a/local/proposal-tracking-lib.R b/local/proposal-tracking-lib.R index 80c4ab2..5efc87c 100644 --- a/local/proposal-tracking-lib.R +++ b/local/proposal-tracking-lib.R @@ -175,37 +175,4 @@ blocked_summary <- function(report, max_pkgs = 15L) { }) } -# Does a registry entry's `platforms` apply to a build on `os` (e.g. -# "ubuntu-2604")? Mirrors bincraft's token match: an entry applies if any of its -# platform tokens is "*", the OS codename, or the distro family ("ubuntu"). -entry_applies_to_os <- function(entry_platforms, os) { - toks <- as.character(unlist(entry_platforms)) - family <- sub("-.*$", "", os) # ubuntu-2604 -> ubuntu - any(toks %in% c("*", os, family)) -} - -# Registry entries present in `current` but not in `base` (matched on -# package|platforms|versions), optionally restricted to those that apply to a -# given `os`. Used by the trial-build gate to build only the entries a PR adds. -new_registry_packages <- function(current, base, os = NULL) { - key <- function(e) { - sprintf( - "%s|%s|%s", - e$package %||% "?", - paste(sort(as.character(unlist(e$platforms))), collapse = ","), - e$versions %||% "?" - ) - } - base_keys <- vapply(base %||% list(), key, character(1L)) - added <- Filter(function(e) !(key(e) %in% base_keys), current %||% list()) - if (!is.null(os)) { - added <- Filter(function(e) entry_applies_to_os(e$platforms, os), added) - } - unique(vapply( - added, - function(e) as.character(e$package %||% ""), - character(1L) - )) -} - `%||%` <- function(a, b) if (is.null(a)) b else a diff --git a/local/propose-patches.R b/local/propose-patches.R index 11085f0..7ff195b 100644 --- a/local/propose-patches.R +++ b/local/propose-patches.R @@ -15,17 +15,10 @@ # # Usage: # PGPASS=... Rscript local/propose-patches.R [--platform P] [--arch A] [--min N] -# [--limit N] # (default) print candidates + validation, take no action # --write append candidates to local/patches/registry.json and # the proposals ledger (commit + open a PR yourself) # --open-issue post/update a Forgejo tracking issue (needs FORGEJO_TOKEN) -# --open-pr write the entries, push the `auto/registry-patch-proposals` -# branch, and open/update a PR autonomously. Uses -# FORGEJO_TOKEN for both the push and the PR API. The -# `trial-build-registry` pipeline is the merge gate. -# --limit N only act on the top-N candidates by failure volume -# (bounded batch; the rest are picked up on the next run) # --json PATH also write the machine-readable candidate list to PATH options(error = function() { @@ -65,8 +58,6 @@ if (is.na(min_count)) { } do_write <- "--write" %in% args do_issue <- "--open-issue" %in% args -do_pr <- "--open-pr" %in% args -limit <- suppressWarnings(as.integer(opt_val("--limit", NA_character_))) if (nchar(Sys.getenv("PGPASS")) == 0L) { stop("PGPASS env var is not set; a DB password is required.") @@ -74,8 +65,6 @@ if (nchar(Sys.getenv("PGPASS")) == 0L) { registry_file <- file.path(script_dir, "patches", "registry.json") ledger_file <- file.path(script_dir, "patches", "proposals-log.json") -# Branch the autonomous PR reuses, so re-runs update one PR instead of piling up. -pr_branch <- "auto/registry-patch-proposals" # --------------------------------------------------------------------------- # Query failing builds (same shape as failing-builds-report.R) @@ -129,8 +118,7 @@ registered_pkgs <- unique(vapply( report <- build_triage_report(failures, registered_pkgs) report <- Filter(function(r) r$build_count >= min_count, report) -# Flatten auto-proposable groups into candidate records, carrying the group's -# build volume so we can prioritise the highest-impact fixes. +# Flatten auto-proposable groups into candidate records. candidates <- list() for (r in report) { if (is.null(r$proposed_entries)) { @@ -142,7 +130,6 @@ for (r in report) { signature = r$signature, confidence = r$confidence, tier = r$tier, - build_count = r$build_count, entry = r$proposed_entries[[pkg]] ) } @@ -186,22 +173,6 @@ if (length(candidates) == 0L) { q(status = 0) } -# Prioritise by failure volume, then apply --limit so one run tackles a bounded -# batch (the rest are picked up on the next run). -candidates <- candidates[order( - -vapply(candidates, function(c) c$build_count %||% 0L, integer(1L)) -)] -deferred <- 0L -if (!is.na(limit) && limit >= 0L && length(candidates) > limit) { - deferred <- length(candidates) - limit - candidates <- utils::head(candidates, limit) - cat(sprintf( - "\nLimiting to top %d candidate(s) by failure volume; %d deferred to a later run.\n", - limit, - deferred - )) -} - cat(sprintf( "\n%d candidate registry %s:\n", length(candidates), @@ -415,160 +386,8 @@ if (do_write) { cat(sprintf("Opened tracking issue #%d.\n", created$number)) } save_ledger(merge_ledger(load_ledger(), new_ledger_records)) -} else if (do_pr) { - forgejo_token <- Sys.getenv("FORGEJO_TOKEN") - if (nchar(forgejo_token) == 0L) { - stop("--open-pr requires FORGEJO_TOKEN (to open the PR).") - } - suppressPackageStartupMessages(library(httr2, quietly = TRUE)) - forgejo_base <- "https://git.devxy.io/api/v1" - repo <- "devxy/build-cran-binaries" - - # Write the entries + ledger, then commit them onto the reused auto branch. - jsonlite::write_json( - merged, - registry_file, - auto_unbox = TRUE, - pretty = TRUE, - null = "null" - ) - save_ledger(merge_ledger(load_ledger(), new_ledger_records)) - - git <- function(...) { - st <- system2("git", c(...), stdout = TRUE, stderr = TRUE) - if (!identical(attr(st, "status"), NULL)) { - stop(sprintf( - "git %s failed:\n%s", - paste(..., collapse = " "), - paste(st, collapse = "\n") - )) - } - invisible(st) - } - git("config", "user.name", Sys.getenv("GIT_USER", "devxy-bot")) - git( - "config", - "user.email", - Sys.getenv("GIT_EMAIL", "bot@devxy.io") - ) - git("checkout", "-B", pr_branch) - git( - "add", - file.path(script_dir, "patches", "registry.json"), - ledger_file - ) - git( - "commit", - "-m", - sprintf( - "feat(patches): auto-propose %d registry %s from classified failures", - length(candidate_entries), - if (length(candidate_entries) == 1L) "entry" else "entries" - ) - ) - # Push over HTTPS with FORGEJO_TOKEN (same token used for the PR API), so no - # separate write-scoped secret is needed. The read-only `origin` clone URL - # can't push, so build an authenticated URL explicitly. - push_target <- sprintf( - "https://%s:%s@git.devxy.io/%s.git", - Sys.getenv("GIT_REMOTE_USER", "pat-s"), - forgejo_token, - repo - ) - git("push", "-f", push_target, sprintf("HEAD:refs/heads/%s", pr_branch)) - - pr_title <- sprintf( - "feat(patches): auto-proposed registry patches (%s)", - now - ) - body_lines <- c( - sprintf( - "_Auto-generated %s by `local/propose-patches.R --open-pr` from classified `single_builds` failures._", - now - ), - "", - sprintf( - "Adds %d known-lever registry %s (top by failure volume%s).", - length(candidate_entries), - if (length(candidate_entries) == 1L) "entry" else "entries", - if (deferred > 0L) { - sprintf("; %d deferred to a later run", deferred) - } else { - "" - } - ), - "", - "**Merge gate:** the `trial-build-registry` pipeline builds each new entry in its target build-env image; merge only once it is green.", - "Novel source diffs and unknown signatures are never auto-proposed.", - "", - "| package | signature | platforms |", - "| --- | --- | --- |" - ) - for (c in candidates) { - body_lines <- c( - body_lines, - sprintf( - "| %s | %s | %s |", - c$package, - c$signature, - toString(as.character(c$entry$platforms)) - ) - ) - } - new_body <- paste(body_lines, collapse = "\n") - - # One PR per reused branch: update if open, else create. - pulls_url <- sprintf( - "%s/repos/%s/pulls?state=open&limit=50", - forgejo_base, - repo - ) - open_pulls <- httr2::request(pulls_url) |> - httr2::req_headers(Authorization = paste("token", forgejo_token)) |> - httr2::req_perform() |> - httr2::resp_body_json(simplifyVector = FALSE) - match_idx <- which(vapply( - open_pulls, - function(p) identical(p$head$ref, pr_branch), - logical(1L) - )) - if (length(match_idx) > 0L) { - num <- open_pulls[[match_idx[1]]]$number - httr2::request(sprintf("%s/repos/%s/pulls/%d", forgejo_base, repo, num)) |> - httr2::req_headers( - Authorization = paste("token", forgejo_token), - `Content-Type` = "application/json" - ) |> - httr2::req_body_json(list(title = pr_title, body = new_body)) |> - httr2::req_method("PATCH") |> - httr2::req_perform() - cat(sprintf("\nUpdated auto-patch PR #%d (branch %s).\n", num, pr_branch)) - } else { - created <- httr2::request(sprintf( - "%s/repos/%s/pulls", - forgejo_base, - repo - )) |> - httr2::req_headers( - Authorization = paste("token", forgejo_token), - `Content-Type` = "application/json" - ) |> - httr2::req_body_json(list( - title = pr_title, - head = pr_branch, - base = "main", - body = new_body - )) |> - httr2::req_perform() |> - httr2::resp_body_json() - cat(sprintf( - "\nOpened auto-patch PR #%d (branch %s).\n", - created$number, - pr_branch - )) - } } else { cat( - "\nDry run: no changes made. Re-run with --write, --open-issue, or --open-pr to act.\n" + "\nDry run: no changes made. Re-run with --write or --open-issue to act.\n" ) } diff --git a/local/tests/test-proposal-tracking-lib.R b/local/tests/test-proposal-tracking-lib.R index 05cd0e9..02a29f1 100644 --- a/local/tests/test-proposal-tracking-lib.R +++ b/local/tests/test-proposal-tracking-lib.R @@ -162,57 +162,6 @@ test_that("blocked_summary lists each dependency and its dependent count", { expect_true(b[[1L]]$packages_truncated) }) -test_that("entry_applies_to_os matches codename, family, and wildcard", { - expect_true(entry_applies_to_os(list("ubuntu-2604"), "ubuntu-2604")) - expect_true(entry_applies_to_os(list("ubuntu"), "ubuntu-2604")) # family - expect_true(entry_applies_to_os(list("*"), "ubuntu-2604")) - expect_true(entry_applies_to_os(list("alpine", "ubuntu-2604"), "ubuntu-2604")) - expect_false(entry_applies_to_os(list("alpine-324"), "ubuntu-2604")) - expect_false(entry_applies_to_os(list("ubuntu-2404"), "ubuntu-2604")) # other codename -}) - -test_that("new_registry_packages returns only added entries for the platform", { - base <- list( - list( - package = "RcppParallel", - platforms = list("alpine", "ubuntu-2604"), - versions = "*" - ) - ) - current <- list( - base[[1L]], # unchanged -> not "new" - list(package = "BFpack", platforms = list("ubuntu-2604"), versions = "*"), - list( - package = "someAlpinePkg", - platforms = list("alpine-324"), - versions = "*" - ) - ) - # For ubuntu-2604: only the newly-added BFpack (RcppParallel is unchanged, - # someAlpinePkg does not apply to this OS). - expect_identical( - new_registry_packages(current, base, os = "ubuntu-2604"), - "BFpack" - ) - # For alpine-324: the alpine package is new and applies. - expect_identical( - new_registry_packages(current, base, os = "alpine-324"), - "someAlpinePkg" - ) - # Without an OS filter, both additions are returned. - expect_setequal( - new_registry_packages(current, base), - c("BFpack", "someAlpinePkg") - ) - # A changed platform set on the same package counts as a new entry. - widened <- list(list( - package = "RcppParallel", - platforms = list("*"), - versions = "*" - )) - expect_identical(new_registry_packages(widened, base), "RcppParallel") -}) - test_that("retirement_candidates flags entries whose package no longer fails", { entries <- list( list(package = "RcppParallel"), diff --git a/local/trial-build-registry.R b/local/trial-build-registry.R deleted file mode 100644 index f649b62..0000000 --- a/local/trial-build-registry.R +++ /dev/null @@ -1,145 +0,0 @@ -#!/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" -ref_ok <- suppressWarnings(system2( - "git", - 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", - 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", - 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)) - tryCatch( - { - bincraft::build_binary_package( - pkg, - tag_limit = 1L, - patches = patches_dir, - archive = FALSE, - upload = FALSE, - store_build_metadata = FALSE - ) - TRUE - }, - error = function(e) { - cat(sprintf("FAILED %s: %s\n", pkg, conditionMessage(e))) - FALSE - } - ) - }, - 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)