# Final Fix Report: bincraft package-patching branch ## C1 Fix — `prepare_patched_repo()` src/contrib layout (`R/patches.R`) **Problem:** `cranlike::add_PACKAGES` was being called for local file indexing, but it delegates to `s3fs::s3_file_exists` internally which only works for S3 paths, not local filesystem paths. Additionally, the tarball and index were placed flat in `repo_dir`, but R/pak resolve `file://` repos via `contrib.url()` which appends `src/contrib` — so the flat layout was invisible to pak. **Fix applied in `R/patches.R` `prepare_patched_repo()`:** - After creating `repo_dir`, creates `contrib <- file.path(repo_dir, "src", "contrib")` and `dir.create(contrib, recursive = TRUE, showWarnings = FALSE)`. - `target` is now `file.path(contrib, ...)` instead of `file.path(repo_dir, ...)`. - `build_patched_binary(entry, version, contrib)` — builds directly into contrib. - Replaced `cranlike::add_PACKAGES(list.files(repo_dir, ...), repo_dir)` with `tools::write_PACKAGES(contrib, type = "source")` — cranlike uses `s3fs` internally and does not work for local paths; `tools::write_PACKAGES` is the correct CRAN-standard indexer for local repos. - Returns `repo_dir` (the root) unchanged so `run_pak_install_with_mutex` keeps prepending `file://` and pak's `contrib.url()` resolves to `/src/contrib`. - Cache (`cache_dir`) stays flat — only the served repo uses src/contrib. ## RED/GREEN Evidence for `available.packages()` test **RED (flat layout, pre-fix):** ``` Warning: cannot open compressed file '.../flat_repo_.../src/contrib/PACKAGES', probable reason 'No such file or directory' pkgfoo found via available.packages(): FALSE ``` **GREEN (src/contrib layout, post-fix):** ``` pkgfoo found via available.packages(): TRUE ``` This directly demonstrates that the flat layout was invisible to R's `available.packages()` / pak resolution. ## New Test — `test-patches.R` Added test: `"prepare_patched_repo src/contrib layout is resolvable by available.packages"` - Builds a minimal valid source package tarball (`pkgfoo_1.0.0.tar.gz`) using real `utils::tar()`. - Mocks only `resolve_patch_version` (→ "1.0.0") and `build_patched_binary` (copies real tarball to `dest_dir`, returns path). - Does NOT mock `cranlike::add_PACKAGES` or `tools::write_PACKAGES` — uses the real indexer. - Asserts: 1. `file.exists(file.path(out, "src", "contrib", "pkgfoo_1.0.0.tar.gz"))` is TRUE. 2. `"pkgfoo" %in% rownames(available.packages(repos = paste0("file://", out), type = "source"))` is TRUE. 3. `ap["pkgfoo", "Version"] == "1.0.0"`. - This test FAILS on the flat layout (warning + empty matrix) and PASSES after the fix. Also updated the existing A5 test (`"prepare_patched_repo serves a cached binary and writes an index"`) to: - Check `file.path(repo, "src", "contrib", "glue_1.0.0.tar.gz")` and `file.path(repo, "src", "contrib", "PACKAGES")` (formerly flat paths). - Mock `tools::write_PACKAGES` instead of `cranlike::add_PACKAGES`. ## Test Results ``` [ FAIL 0 | WARN 0 | SKIP 1 | PASS 41 ] ``` (1 skip: E2E test gated on `BINCRAFT_PATCH_E2E` env var, by design.) ## Doc Changes **`R/build_binaries.R`:** - Added `#' @template param-patches` to `execute_package_builds` roxygen block. - Added `#' @template param-patches` to `handle_system_dependencies` roxygen block. **`R/install_helpers.R`:** - Added `#' @param patched_repo Optional path to a local patched-binary repo to prepend to pak's repos for this install (internal).` to `run_pak_install_with_mutex` roxygen block. **`devtools::document()` output:** ``` Writing 'execute_package_builds.Rd' Writing 'handle_system_dependencies.Rd' Writing 'run_pak_install_with_mutex.Rd' ``` All three Rd files regenerated successfully. ## Deferred: I2 Duplicate registry entries resolving to the same `package_version` silently overwrite each other in `prepare_patched_repo` — the last one wins because both write to the same `target` path. This is a known limitation and is deferred; no action taken.