150 lines
4.7 KiB
Markdown
150 lines
4.7 KiB
Markdown
### Task A1: Registry loading and normalization
|
|
|
|
**Files:**
|
|
- Create: `R/patches.R`
|
|
- Test: `tests/testthat/test-patches.R`
|
|
|
|
**Interfaces:**
|
|
- Produces: `load_patch_registry(patches_dir)` → `list()` of normalized entries; each entry is a named list with `package`, `versions`, `platforms` (character vector), `env` (named list), `configure_args` (character), `makevars` (named list), `reason`, and `patch_path` (absolute path or `NULL`). `normalize_patch_entry(entry, patches_dir)` → one normalized entry; errors on missing required field or missing patch file.
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
```r
|
|
# tests/testthat/test-patches.R
|
|
test_that("load_patch_registry parses and normalizes entries", {
|
|
dir <- withr::local_tempdir()
|
|
writeLines("--- a patch ---", file.path(dir, "fix.patch"))
|
|
jsonlite::write_json(
|
|
list(list(
|
|
package = "RcppParallel", versions = "*",
|
|
platforms = list("alpine", "ubuntu-2604"),
|
|
env = list(RCPP_PARALLEL_USE_TBB = "0"),
|
|
patch = "fix.patch", reason = "bundled TBB fails"
|
|
)),
|
|
file.path(dir, "registry.json"), auto_unbox = TRUE
|
|
)
|
|
|
|
reg <- load_patch_registry(dir)
|
|
|
|
expect_length(reg, 1L)
|
|
expect_identical(reg[[1L]]$package, "RcppParallel")
|
|
expect_identical(reg[[1L]]$platforms, c("alpine", "ubuntu-2604"))
|
|
expect_identical(reg[[1L]]$env$RCPP_PARALLEL_USE_TBB, "0")
|
|
expect_identical(reg[[1L]]$configure_args, character(0L))
|
|
expect_true(file.exists(reg[[1L]]$patch_path))
|
|
})
|
|
|
|
test_that("load_patch_registry returns empty list when no registry", {
|
|
expect_identical(load_patch_registry(NULL), list())
|
|
expect_identical(load_patch_registry(withr::local_tempdir()), list())
|
|
})
|
|
|
|
test_that("normalize_patch_entry errors on missing required field", {
|
|
expect_error(
|
|
normalize_patch_entry(list(package = "x"), tempdir()),
|
|
"missing required field"
|
|
)
|
|
})
|
|
|
|
test_that("normalize_patch_entry errors on missing patch file", {
|
|
expect_error(
|
|
normalize_patch_entry(
|
|
list(package = "x", versions = "*", platforms = "alpine",
|
|
reason = "r", patch = "nope.patch"),
|
|
tempdir()
|
|
),
|
|
"does not exist"
|
|
)
|
|
})
|
|
```
|
|
|
|
- [ ] **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 load_patch_registry".
|
|
|
|
- [ ] **Step 3: Write minimal implementation**
|
|
|
|
```r
|
|
# R/patches.R
|
|
|
|
#' Load and validate the patch registry
|
|
#'
|
|
#' Reads `registry.json` from `patches_dir` and returns normalized entries.
|
|
#'
|
|
#' @param patches_dir Directory containing `registry.json` and any patch files,
|
|
#' or `NULL` to disable patching.
|
|
#' @return A list of normalized patch entries (possibly empty).
|
|
#' @keywords internal
|
|
load_patch_registry <- function(patches_dir) {
|
|
if (is.null(patches_dir)) {
|
|
return(list())
|
|
}
|
|
registry_file <- file.path(patches_dir, "registry.json")
|
|
if (!file.exists(registry_file)) {
|
|
log_warn(sprintf(
|
|
"Patch directory {.path %s} has no registry.json; patching disabled.",
|
|
patches_dir
|
|
))
|
|
return(list())
|
|
}
|
|
raw <- jsonlite::fromJSON(registry_file, simplifyVector = FALSE)
|
|
lapply(raw, normalize_patch_entry, patches_dir = patches_dir)
|
|
}
|
|
|
|
#' Normalize and validate a single patch registry entry
|
|
#'
|
|
#' @param entry A list parsed from `registry.json`.
|
|
#' @param patches_dir Directory used to resolve a relative `patch` path.
|
|
#' @return The entry with defaults filled and `patch_path` resolved.
|
|
#' @keywords internal
|
|
normalize_patch_entry <- function(entry, patches_dir) {
|
|
required <- c("package", "versions", "platforms", "reason")
|
|
missing <- setdiff(required, names(entry))
|
|
if (length(missing) > 0L) {
|
|
stop(
|
|
sprintf("Patch entry is missing required field(s): %s", toString(missing)),
|
|
call. = FALSE
|
|
)
|
|
}
|
|
entry$platforms <- as.character(unlist(entry$platforms))
|
|
entry$env <- if (is.null(entry$env)) list() else entry$env
|
|
entry$configure_args <- if (is.null(entry$configure_args)) {
|
|
character(0L)
|
|
} else {
|
|
as.character(unlist(entry$configure_args))
|
|
}
|
|
entry$makevars <- if (is.null(entry$makevars)) list() else entry$makevars
|
|
if (!is.null(entry$patch)) {
|
|
patch_path <- file.path(patches_dir, entry$patch)
|
|
if (!file.exists(patch_path)) {
|
|
stop(
|
|
sprintf(
|
|
"Patch file '%s' for package '%s' does not exist.",
|
|
patch_path, entry$package
|
|
),
|
|
call. = FALSE
|
|
)
|
|
}
|
|
entry$patch_path <- patch_path
|
|
} else {
|
|
entry$patch_path <- NULL
|
|
}
|
|
entry
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `Rscript -e 'devtools::load_all("."); testthat::test_file("tests/testthat/test-patches.R")'`
|
|
Expected: PASS (4 tests).
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add R/patches.R tests/testthat/test-patches.R
|
|
git commit -m "feat(patches): load and validate the patch registry"
|
|
```
|
|
|
|
---
|
|
|