From 03d1801d7e386126aacd3b12ee43def58ed23a6e Mon Sep 17 00:00:00 2001 From: Jonathan Keane Date: Wed, 4 Mar 2026 08:13:04 -0600 Subject: [PATCH 1/3] Failing tests --- tests/testthat/test-bundlePackage.R | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/testthat/test-bundlePackage.R b/tests/testthat/test-bundlePackage.R index fcb91f995..ee8954f64 100644 --- a/tests/testthat/test-bundlePackage.R +++ b/tests/testthat/test-bundlePackage.R @@ -71,6 +71,58 @@ test_that("can capture deps from renv lockfile", { expect_equal(list.files(app_dir), c("foo.R", "renv.lock")) }) +test_that("can capture deps from renv lockfile in custom location (RENV_PATHS_LOCKFILE)", { + skip_if_not_installed("foreign") + skip_if_not_installed("MASS") + + withr::local_options(renv.verbose = FALSE) + + app_dir <- local_temp_app(list(foo.R = "library(foreign); library(MASS)")) + + # Create renv.lock in the standard location first + + renv::snapshot(app_dir, prompt = FALSE) + + # Move the lockfile to a custom location outside the app dir + custom_lock_dir <- withr::local_tempdir() + custom_lock_path <- file.path(custom_lock_dir, "renv.lock") + file.rename(file.path(app_dir, "renv.lock"), custom_lock_path) + + # Set RENV_PATHS_LOCKFILE to point to the custom location + withr::local_envvar(RENV_PATHS_LOCKFILE = custom_lock_path) + + # This should find the lockfile at the custom location and capture deps, + # but currently fails because renvLockFile() only looks in the app dir. + expect_snapshot(pkgs <- bundlePackages(app_dir)) + + # renv is included by the renv.lock + expect_named(pkgs, c("foreign", "MASS", "renv"), ignore.order = TRUE) + expect_named(pkgs$foreign, c("Source", "Repository", "description")) + expect_named(pkgs$MASS, c("Source", "Repository", "description")) +}) + +test_that("can capture deps from renv lockfile with renv profile", { + skip_if_not_installed("foreign") + skip_if_not_installed("MASS") + + withr::local_options(renv.verbose = FALSE) + + app_dir <- local_temp_app(list(foo.R = "library(foreign); library(MASS)")) + + # Set RENV_PROFILE so renv resolves the lockfile from the profile path + withr::local_envvar(RENV_PROFILE = "testing") + renv::snapshot(app_dir, prompt = FALSE) + + # This should find the lockfile at the profile location and capture deps, + # but currently fails because renvLockFile() only looks in the app root. + expect_snapshot(pkgs <- bundlePackages(app_dir)) + + # renv is included by the renv.lock + expect_named(pkgs, c("foreign", "MASS", "renv"), ignore.order = TRUE) + expect_named(pkgs$foreign, c("Source", "Repository", "description")) + expect_named(pkgs$MASS, c("Source", "Repository", "description")) +}) + test_that("can capture deps with packrat even when renv lockfile present", { skip_if_not_installed("foreign") skip_if_not_installed("MASS") From 901fa54f3c42dfb586a8bc0ebad075848bb588e9 Mon Sep 17 00:00:00 2001 From: Jonathan Keane Date: Sat, 7 Mar 2026 11:16:58 -0600 Subject: [PATCH 2/3] The fix --- R/bundlePackage.R | 2 +- R/bundlePackageRenv.R | 34 ++++++++++++++++++++++++++ tests/testthat/_snaps/bundlePackage.md | 29 ++++++++++++++++++++++ tests/testthat/test-bundlePackage.R | 33 ++++++++++++++++++++++--- 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/R/bundlePackage.R b/R/bundlePackage.R index 2760fdb86..9207ce491 100644 --- a/R/bundlePackage.R +++ b/R/bundlePackage.R @@ -58,7 +58,7 @@ computePackageDependencies <- function( extraPackages, verbose = verbose ) - } else if (file.exists(renvLockFile(bundleDir))) { + } else if (ensureRenvLockFile(bundleDir)) { # This ignores extraPackages; if you're using a lockfile it's your # responsibility to install any other packages you need taskStart(quiet, "Capturing R dependencies from renv.lock") diff --git a/R/bundlePackageRenv.R b/R/bundlePackageRenv.R index 9548e255d..c352ac5fc 100644 --- a/R/bundlePackageRenv.R +++ b/R/bundlePackageRenv.R @@ -30,6 +30,10 @@ snapshotRenvDependencies <- function( progress = FALSE ) renv::snapshot(bundleDir, packages = deps$Package, prompt = FALSE) + # renv::snapshot() respects RENV_PATHS_LOCKFILE and renv profiles, so the + # lockfile may have been written to a non-standard location. Copy it into the + # bundle dir so parseRenvDependencies() can find it. + ensureRenvLockFile(bundleDir) defer(removeRenv(bundleDir)) parseRenvDependencies(bundleDir, snapshot = TRUE) @@ -172,6 +176,36 @@ renvLockFile <- function(bundleDir) { file.path(bundleDir, "renv.lock") } +# Ensure the renv lockfile is at the standard bundle location. +# If found at a custom location (via RENV_PATHS_LOCKFILE or renv profiles), +# copies it into bundleDir/renv.lock, overwriting any stale lockfile. +# Returns TRUE if the lockfile is available, FALSE otherwise. +ensureRenvLockFile <- function(bundleDir) { + standard <- renvLockFile(bundleDir) + resolved <- renv::paths$lockfile(project = bundleDir) + + # If the resolved file exists, and is different from the standard location, + # copy it to the standard location. + if (file.exists(resolved)) { + # Normalize to avoid self-copy (e.g., /var vs /private/var on macOS) + if (normalizePath(resolved, mustWork = FALSE) != normalizePath(standard, mustWork = FALSE)) { + if (file.exists(standard)) { + cli::cli_warn(c( + "Using lockfile at {.path {resolved}} instead of {.path {standard}}.", + i = "The lockfile in the project root may be outdated.", + i = "Remove it to silence this warning." + )) + } + if (!file.copy(resolved, standard, overwrite = TRUE)) { + cli::cli_abort("Failed to copy lockfile from {.path {resolved}} to {.path {standard}}.") + } + } + return(TRUE) + } + + file.exists(standard) +} + removeRenv <- function(path, lockfile = TRUE) { if (lockfile) { unlink(renvLockFile(path)) diff --git a/tests/testthat/_snaps/bundlePackage.md b/tests/testthat/_snaps/bundlePackage.md index ffe301a87..f6a923d6a 100644 --- a/tests/testthat/_snaps/bundlePackage.md +++ b/tests/testthat/_snaps/bundlePackage.md @@ -30,6 +30,35 @@ i Capturing R dependencies from renv.lock v Found 3 dependencies +# can capture deps from renv lockfile in custom location (RENV_PATHS_LOCKFILE) + + Code + pkgs <- bundlePackages(app_dir) + Message + i Capturing R dependencies from renv.lock + v Found 3 dependencies + +# warns when custom lockfile overwrites existing standard lockfile + + Code + pkgs <- bundlePackages(app_dir) + Condition + Warning: + Using lockfile at '' instead of ''. + i The lockfile in the project root may be outdated. + i Remove it to silence this warning. + Message + i Capturing R dependencies from renv.lock + v Found 3 dependencies + +# can capture deps from renv lockfile with renv profile + + Code + pkgs <- bundlePackages(app_dir) + Message + i Capturing R dependencies from renv.lock + v Found 3 dependencies + # can capture deps with packrat even when renv lockfile present Code diff --git a/tests/testthat/test-bundlePackage.R b/tests/testthat/test-bundlePackage.R index ee8954f64..d79e0bb3f 100644 --- a/tests/testthat/test-bundlePackage.R +++ b/tests/testthat/test-bundlePackage.R @@ -91,8 +91,7 @@ test_that("can capture deps from renv lockfile in custom location (RENV_PATHS_LO # Set RENV_PATHS_LOCKFILE to point to the custom location withr::local_envvar(RENV_PATHS_LOCKFILE = custom_lock_path) - # This should find the lockfile at the custom location and capture deps, - # but currently fails because renvLockFile() only looks in the app dir. + # Should find the lockfile at the custom location and capture deps. expect_snapshot(pkgs <- bundlePackages(app_dir)) # renv is included by the renv.lock @@ -101,6 +100,33 @@ test_that("can capture deps from renv lockfile in custom location (RENV_PATHS_LO expect_named(pkgs$MASS, c("Source", "Repository", "description")) }) +test_that("warns when custom lockfile overwrites existing standard lockfile", { + skip_if_not_installed("foreign") + skip_if_not_installed("MASS") + + withr::local_options(renv.verbose = FALSE) + + app_dir <- local_temp_app(list(foo.R = "library(foreign); library(MASS)")) + + # Create renv.lock in the standard location + renv::snapshot(app_dir, prompt = FALSE) + + # Copy the lockfile to a custom location, leaving the original in place + custom_lock_dir <- withr::local_tempdir() + custom_lock_path <- file.path(custom_lock_dir, "renv.lock") + file.copy(file.path(app_dir, "renv.lock"), custom_lock_path) + + # Set RENV_PATHS_LOCKFILE to point to the custom location + withr::local_envvar(RENV_PATHS_LOCKFILE = custom_lock_path) + + # Should warn about overwriting the standard lockfile + expect_snapshot(pkgs <- bundlePackages(app_dir), transform = function(x) { + gsub("'[^']+'", "''", x) + }) + + expect_named(pkgs, c("foreign", "MASS", "renv"), ignore.order = TRUE) +}) + test_that("can capture deps from renv lockfile with renv profile", { skip_if_not_installed("foreign") skip_if_not_installed("MASS") @@ -113,8 +139,7 @@ test_that("can capture deps from renv lockfile with renv profile", { withr::local_envvar(RENV_PROFILE = "testing") renv::snapshot(app_dir, prompt = FALSE) - # This should find the lockfile at the profile location and capture deps, - # but currently fails because renvLockFile() only looks in the app root. + # Should find the lockfile at the profile location and capture deps. expect_snapshot(pkgs <- bundlePackages(app_dir)) # renv is included by the renv.lock From c70493180cdf30b41a29bcb59ac53fa5b45b956e Mon Sep 17 00:00:00 2001 From: Jonathan Keane Date: Sat, 7 Mar 2026 11:29:49 -0600 Subject: [PATCH 3/3] air --- R/bundlePackageRenv.R | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/R/bundlePackageRenv.R b/R/bundlePackageRenv.R index c352ac5fc..c85112f12 100644 --- a/R/bundlePackageRenv.R +++ b/R/bundlePackageRenv.R @@ -188,7 +188,10 @@ ensureRenvLockFile <- function(bundleDir) { # copy it to the standard location. if (file.exists(resolved)) { # Normalize to avoid self-copy (e.g., /var vs /private/var on macOS) - if (normalizePath(resolved, mustWork = FALSE) != normalizePath(standard, mustWork = FALSE)) { + if ( + normalizePath(resolved, mustWork = FALSE) != + normalizePath(standard, mustWork = FALSE) + ) { if (file.exists(standard)) { cli::cli_warn(c( "Using lockfile at {.path {resolved}} instead of {.path {standard}}.", @@ -197,7 +200,9 @@ ensureRenvLockFile <- function(bundleDir) { )) } if (!file.copy(resolved, standard, overwrite = TRUE)) { - cli::cli_abort("Failed to copy lockfile from {.path {resolved}} to {.path {standard}}.") + cli::cli_abort( + "Failed to copy lockfile from {.path {resolved}} to {.path {standard}}." + ) } } return(TRUE)