fix(patches): validator portability, clearer docs, broader hook trigger
This commit is contained in:
parent
a826a27246
commit
f3f3bfa20d
1 changed files with 2428 additions and 8 deletions
165
.superpowers/handoff/task-A5-brief.md
Normal file
165
.superpowers/handoff/task-A5-brief.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
### 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**
|
||||
|
||||
```r
|
||||
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`)**
|
||||
|
||||
```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:
|
||||
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Loading…
Reference in a new issue