build-cran-binaries/.superpowers/handoff/task-A5-brief.md

5.4 KiB

Task A5: Orchestrate the local patched repo (cache + index)

Files:

  • Modify: R/patches.R
  • Test: tests/testthat/test-patches.R

Interfaces:

  • Consumes: all helpers above.

  • Produces: prepare_patched_repo(patches_dir, platform, arch, r_minor, cache_dir, repo_dir) → path to a src/contrib-style dir containing patched binaries + a PACKAGES index, or NULL when nothing matched/built. On a cache hit it copies the cached tarball into repo_dir; on a miss it builds, then writes the result into cache_dir.

  • Step 1: Write the failing test

test_that("prepare_patched_repo returns NULL when no entries match", {
  dir <- withr::local_tempdir()
  jsonlite::write_json(
    list(list(package = "A", versions = "*", platforms = list("redhat"),
              reason = "r")),
    file.path(dir, "registry.json"), auto_unbox = TRUE
  )
  expect_null(
    prepare_patched_repo(dir, "ubuntu-2604", "amd64", "4.5",
                         cache_dir = withr::local_tempdir(),
                         repo_dir = withr::local_tempdir())
  )
})

test_that("prepare_patched_repo serves a cached binary and writes an index", {
  dir <- withr::local_tempdir()
  jsonlite::write_json(
    list(list(package = "glue", versions = "*", platforms = list("*"),
              env = list(A = "1"), reason = "r")),
    file.path(dir, "registry.json"), auto_unbox = TRUE
  )
  cache <- withr::local_tempdir()
  repo <- withr::local_tempdir()

  local_mocked_bindings(
    resolve_patch_version = function(entry) "1.0.0",
    build_patched_binary = function(entry, version, dest_dir) {
      f <- file.path(dest_dir, sprintf("%s_%s.tar.gz", entry$package, version))
      writeLines("fake binary", f)
      f
    }
  )

  out <- prepare_patched_repo(dir, "ubuntu-2604", "amd64", "4.5",
                              cache_dir = cache, repo_dir = repo)

  expect_identical(out, repo)
  expect_true(file.exists(file.path(repo, "glue_1.0.0.tar.gz")))
  expect_true(file.exists(file.path(repo, "PACKAGES")))
  # The build result was cached under the key.
  expect_length(list.files(cache, pattern = "^glue_1.0.0_.*\\.tar\\.gz$"), 1L)
})
  • Step 2: Run test to verify it fails

Run: Rscript -e 'devtools::load_all("."); testthat::test_file("tests/testthat/test-patches.R")' Expected: FAIL with "could not find function prepare_patched_repo".

  • Step 3: Write minimal implementation (append to R/patches.R)
#' Prepare a local repo of patched binaries for the current build
#'
#' For each registry entry matching the current platform, ensures a patched
#' binary is present in `repo_dir` (from `cache_dir` if available, else built
#' and then cached) and writes a `PACKAGES` index over them.
#'
#' @param patches_dir Directory with `registry.json`, or `NULL`.
#' @param platform Build platform, e.g. `"ubuntu-2604"`.
#' @param arch Build arch, e.g. `"amd64"`.
#' @param r_minor R `"major.minor"` string, e.g. `"4.5"`.
#' @param cache_dir Persistent cache for patched binaries.
#' @param repo_dir Directory to assemble the local repo in.
#' @return `repo_dir` if at least one patched binary was produced, else `NULL`.
#' @keywords internal
prepare_patched_repo <- function(
  patches_dir,
  platform,
  arch,
  r_minor,
  cache_dir = file.path("/mnt", "cache", "patched-binaries"),
  repo_dir = tempfile("patched_repo_")
) {
  entries <- match_patch_entries(
    load_patch_registry(patches_dir), platform, arch
  )
  if (length(entries) == 0L) {
    return(NULL)
  }

  dir.create(repo_dir, recursive = TRUE, showWarnings = FALSE)
  dir.create(cache_dir, recursive = TRUE, showWarnings = FALSE)

  produced <- 0L
  for (entry in entries) {
    version <- resolve_patch_version(entry)
    if (is.na(version)) {
      log_warn(sprintf(
        "No CRAN version of {.pkg %s} satisfies '%s'; patch skipped.",
        entry$package, entry$versions
      ))
      next
    }

    key <- patch_cache_key(entry, version, platform, arch, r_minor)
    cached <- file.path(cache_dir, sprintf("%s.tar.gz", key))
    target <- file.path(
      repo_dir, sprintf("%s_%s.tar.gz", entry$package, version)
    )

    if (file.exists(cached)) {
      log_info(sprintf(
        "Using cached patched binary for {.pkg %s} %s.",
        entry$package, version
      ))
      file.copy(cached, target, overwrite = TRUE)
    } else {
      log_info(sprintf(
        "Applying patch to {.pkg %s} %s [%s]: %s",
        entry$package, version, describe_patch(entry), entry$reason
      ))
      built <- build_patched_binary(entry, version, repo_dir)
      if (is.null(built)) {
        next
      }
      if (!identical(normalizePath(built), normalizePath(target))) {
        file.copy(built, target, overwrite = TRUE)
      }
      file.copy(target, cached, overwrite = TRUE)
    }
    produced <- produced + 1L
  }

  if (produced == 0L) {
    return(NULL)
  }
  cranlike::add_PACKAGES(
    list.files(repo_dir, pattern = "\\.tar\\.gz$"),
    repo_dir
  )
  repo_dir
}
  • Step 4: Run test to verify it passes

Run: Rscript -e 'devtools::load_all("."); testthat::test_file("tests/testthat/test-patches.R")' Expected: PASS.

  • Step 5: Update imports and document

Add to DESCRIPTION Imports: (alphabetical) jsonlite if not present, and ensure pkgsearch, pkgbuild, cranlike, withr are listed (they are). Then:

Rscript -e 'devtools::document()'
git add R/patches.R tests/testthat/test-patches.R DESCRIPTION NAMESPACE
git commit -m "feat(patches): orchestrate local patched-binary repo with caching"