diff --git a/NEWS.md b/NEWS.md index 8536d9ef..e11af4f7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,11 @@ longer used to detect if these are actually git-like. This prevents issues if you name a CRAN-like repository something like "GitHub". (#747) +- `restore()` will warn when a package's lockfile hash does not match the hash + computed from the installed DESCRIPTION file, which indicates the lockfile was + generated from inaccurate package metadata and will cause repeated cache + misses. (#750) + # packrat 0.9.3 - Update vendored `renv` with support for additional Linux distributions when diff --git a/R/restore.R b/R/restore.R index 5be4caab..6cd85d22 100644 --- a/R/restore.R +++ b/R/restore.R @@ -773,6 +773,21 @@ installPkg <- function(pkgRecord, project, repos, lib = libDir(project)) { ) } else { hash <- hash(descPath) + if (!isTRUE(pkgRecord$hash == hash)) { + warning( + "Package '", + pkgRecord$name, + "': expected hash ", + pkgRecord$hash, + " but got ", + hash, + " after installation. The DESCRIPTION fields used for hashing ", + "(Package, Version, Depends, Imports, Suggests, LinkingTo, and ", + "Remote fields such as GithubSHA1, RemoteType, RemoteHost, ", + "RemoteRepo, RemoteUsername, RemoteRef, RemoteSha, RemoteSubdir) ", + "may differ from what was recorded in the lockfile." + ) + } moveInstalledPackageToCache( packagePath = pkgInstallPath, hash = hash, diff --git a/tests/testthat/test-cache.R b/tests/testthat/test-cache.R index 6be7e9fc..80fd97f5 100644 --- a/tests/testthat/test-cache.R +++ b/tests/testthat/test-cache.R @@ -62,6 +62,49 @@ test_that("package installation when configured with a a cache uses the cache", expect_true(is.symlink(packageDir), packageDir) }) +test_that("packrat warns when lockfile hash does not match installed hash", { + skip_on_cran() + skip_on_os("windows") + + scopeTestContext() + + projRoot <- cloneTestProject("healthy") + libRoot <- file.path(projRoot, "packrat", "lib") + srcRoot <- file.path(projRoot, "packrat", "src") + + theCache <- tempfile("packrat-cache-") + ensureDirectory(theCache) + Sys.setenv(R_PACKRAT_CACHE_DIR = theCache) + on.exit( + { + Sys.unsetenv("R_PACKRAT_CACHE_DIR") + unlink(theCache, recursive = TRUE) + }, + add = TRUE + ) + init(projRoot, options = list(local.repos = "packages"), enter = FALSE) + + set_opts(project = projRoot, use.cache = TRUE) + on.exit(set_opts(use.cache = FALSE, project = projRoot), add = TRUE) + + # Replace one package's hash in the lockfile with a bogus value + lockfilePath <- file.path(projRoot, "packrat", "packrat.lock") + lockfileContent <- readLines(lockfilePath) + hashLines <- grep("^Hash:", lockfileContent) + lockfileContent[hashLines[1]] <- "Hash: 00000000000000000000000000000000" + writeLines(lockfileContent, lockfilePath) + + # Remove lib and src so restore must reinstall + unlink(libRoot, recursive = TRUE) + unlink(srcRoot, recursive = TRUE) + + # Restore should warn about the hash mismatch + expect_warning( + restore(projRoot, overwrite.dirty = TRUE, prompt = FALSE, restart = FALSE), + "expected hash.*after installation" + ) +}) + test_that("packrat uses the untrusted cache when instructed", { skip_on_cran() skip_on_os("windows") @@ -70,7 +113,13 @@ test_that("packrat uses the untrusted cache when instructed", { # pretend that we're Posit Connect Sys.setenv(POSIT_CONNECT = 1) - on.exit(Sys.unsetenv("POSIT_CONNECT"), add = TRUE) + on.exit( + { + Sys.unsetenv("POSIT_CONNECT") + options(packrat.untrusted.packages = NULL) + }, + add = TRUE + ) projRoot <- cloneTestProject("healthy") libRoot <- file.path(projRoot, "packrat", "lib")