Repository: rstudio/rsconnect
Pull request #1157: Mimic major-minor Connect behavior
Author: login: amol-
Created: 2025-05-09T13:14:03Z | Merged: 2025-05-09T19:16:47Z

Adapt all version numbers that don't have a constraint to a compatible constraint that accepts any patch release.




Diff:
diff --git a/R/bundle.R b/R/bundle.R
index 20f46c61..cf8d1240 100644
--- a/R/bundle.R
+++ b/R/bundle.R
@@ -139,7 +139,12 @@ versionFromLockfile <- function(appDir) {
   tryCatch(
     {
       lockfile <- suppressWarnings(renv::lockfile_read(project = appDir))
-      paste("~=", lockfile$R$Version)
+      v <- lockfile$R$Version
+      # only major specified “3” → ~=3.0 → >=3.0,<4.0
+      # major and minor specified “3.8” or “3.8.11” → ~=3.8.0 → >=3.8.0,<3.9.0
+      parts <- strsplit(v, "\\.")[[1]]
+      new_parts <- c(head(parts, 2), "0")
+      paste0("~=", paste(new_parts, collapse = "."))
     },
     error = function(e) {
       return(NULL)
diff --git a/tests/testthat/test-writeManifest.R b/tests/testthat/test-writeManifest.R
index 4de7cbf9..c4e8e921 100644
--- a/tests/testthat/test-writeManifest.R
+++ b/tests/testthat/test-writeManifest.R
@@ -325,12 +325,54 @@ test_that("environment.r.requires - renv.lock - Existing R version is added", {
   renv::snapshot(appDir, prompt = FALSE)
 
   manifest <- makeManifest(appDir)
+
+  maj <- R.Version()$major
+  min1 <- strsplit(R.Version()$minor, "\\.")[[1]][1]
+  expected <- sprintf("~=%s.%s.0", maj, min1)
   expect_equal(
     manifest$environment$r$requires,
-    paste("~= ", R.Version()$major, ".", R.Version()$minor, sep = "")
+    expected
   )
 })
 
+test_that("versionFromLockfile formats various R versions with patch .0", {
+  versions <- c("3", "3.8", "3.8.11", "3.25", "4.0.2", "10.5.3")
+  expected <- c(
+    "~=3.0",
+    "~=3.8.0",
+    "~=3.8.0",
+    "~=3.25.0",
+    "~=4.0.0",
+    "~=10.5.0"
+  )
+
+  for (i in seq_along(versions)) {
+    # Create a temporary directory with a renv.lock file
+    # containing the specified R version
+    appDir <- local_temp_app()
+    jsonlite::write_json(
+      list(R = list(Version = versions[i])),
+      path = file.path(appDir, "renv.lock"),
+      auto_unbox = TRUE,
+      pretty = TRUE
+    )
+
+    res <- versionFromLockfile(appDir)
+    expect_equal(
+      res,
+      expected[i],
+      info = paste(
+        "Input:",
+        versions[i],
+        "→ got",
+        res,
+        "but expected",
+        expected[i]
+      )
+    )
+  }
+})
+
 test_that("environment.r.requires - DESCRIPTION file takes precedence over renv.lock", {
   skip_if_not_installed("foreign")
   skip_if_not_installed("MASS")


Reviews:
user: login: marcosnav
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T18:21:29Z
user: login: aronatkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T18:59:45Z
user: login: aronatkins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T19:48:41Z

Review comments:
diff_hunk: @@ -325,12 +325,55 @@ test_that("environment.r.requires - renv.lock - Existing R version is added", {
   renv::snapshot(appDir, prompt = FALSE)
 
   manifest <- makeManifest(appDir)
+
+  maj <- R.Version()$major
+  min1 <- strsplit(R.Version()$minor, "\\.")[[1]][1]
+  expected <- sprintf("~=%s.%s.0", maj, min1)
   expect_equal(
     manifest$environment$r$requires,
-    paste("~= ", R.Version()$major, ".", R.Version()$minor, sep = "")
+    expected
   )
 })
 
+test_that("versionFromLockfile formats various R versions with patch .0", {
+  versions <- c("3", "3.8", "3.8.11", "3.25", "4.0.2", "10.5.3")
+  expected <- c(
+    "~=3.0",
+    "~=3.8.0",
+    "~=3.8.0",
+    "~=3.25.0",
+    "~=4.0.0",
+    "~=10.5.0"
+  )
path: tests/testthat/test-writeManifest.R
user: login: marcosnav
body: Appreciate the multiple examples testing!
created_at: 2025-05-09T18:21:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -325,12 +325,55 @@ test_that("environment.r.requires - renv.lock - Existing R version is added", {
   renv::snapshot(appDir, prompt = FALSE)
 
   manifest <- makeManifest(appDir)
+
+  maj <- R.Version()$major
+  min1 <- strsplit(R.Version()$minor, "\\.")[[1]][1]
+  expected <- sprintf("~=%s.%s.0", maj, min1)
   expect_equal(
     manifest$environment$r$requires,
-    paste("~= ", R.Version()$major, ".", R.Version()$minor, sep = "")
+    expected
   )
 })
 
+test_that("versionFromLockfile formats various R versions with patch .0", {
+  versions <- c("3", "3.8", "3.8.11", "3.25", "4.0.2", "10.5.3")
+  expected <- c(
+    "~=3.0",
+    "~=3.8.0",
+    "~=3.8.0",
+    "~=3.25.0",
+    "~=4.0.0",
+    "~=10.5.0"
+  )
+
+  for (i in seq_along(versions)) {
+    # Create a temporary directory with a renv.lock file
+    # containing the specified R version
+    appDir <- tempfile("app")
path: tests/testthat/test-writeManifest.R
user: login: aronatkins
body: Helpers like `local_temp_app()` use `withr::local_tempdir()` to create temporary directories containing named files which are automatically removed as tests complete.
created_at: 2025-05-09T18:59:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -349,8 +349,7 @@ test_that("versionFromLockfile formats various R versions with patch .0", {
   for (i in seq_along(versions)) {
     # Create a temporary directory with a renv.lock file
     # containing the specified R version
-    appDir <- tempfile("app")
-    dir.create(appDir)
+    appDir <- local_temp_app()
path: tests/testthat/test-writeManifest.R
user: login: aronatkins
body: You also could have let `local_temp_app()` handle file creation:

```r
appdir <- local_temp_app(list
     renv.lock = jsonlite::toJSON(...)   
))
```
created_at: 2025-05-09T19:48:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: marcosnav
created_at: 2025-05-09T19:16:35Z
body: Merging now, it'd be great for this one to land in the next release.
author_association: CONTRIBUTOR
user: login: marcosnav
created_at: 2025-05-09T20:18:13Z
body: Screenshots of manual testing manifest generation for these latest changes.

**Respects DESCRIPTION R version as is, over renv.lock**
<img width="1949" alt="Screenshot 2025-05-09 at 2 11 09 p m" src="https://github.com/user-attachments/assets/54dde891-3552-41bd-a13e-5a5984ffc3e9" />

---

**renv.lock `4.3.0` => `environment.r.requires ~= 4.3.0`**
<img width="1825" alt="Screenshot 2025-05-09 at 2 12 26 p m" src="https://github.com/user-attachments/assets/ec344069-730f-4716-91a6-edef6fe46b84" />

---

**renv.lock `4.3.2` => `environment.r.requires ~= 4.3.0`**
<img width="1826" alt="Screenshot 2025-05-09 at 2 13 23 p m" src="https://github.com/user-attachments/assets/befb494b-d646-4d61-95f4-23a826e52ac5" />


author_association: CONTRIBUTOR