Solidify logic for picking rspm vs mran
MRAN doesn't seem to have R 4.1 specific snapshots, so let's
default to RSPM for anything 4.1+.
Otherwise, snapshot dates in 2022 will result in using rspm
MODIFY
repo2docker/buildpacks/r.py
repo2docker/buildpacks/r.py
@@ -185,15 +185,14 @@ class RBuildPack(PythonBuildPack):
return super().get_packages().union(packages)
- def get_cran_mirror_url(self, snapshot_date):
- # Call the API to find out if we have a snapshot available for the given date.
- # If so, use the URL for that snapshot. If not, fall back to MRAN.
+ def get_rspm_snapshot_url(self, snapshot_date, max_days_prior=7):
+ for i in range(max_days_prior):
snapshots = requests.post(
"https://packagemanager.rstudio.com/__api__/url",
# Ask for midnight UTC snapshot
json={
"repo": "all",
- "snapshot": snapshot_date.strftime("%Y-%m-%dT00:00:00Z"),
+ "snapshot": (snapshot_date - datetime.timedelta(days=i)).strftime("%Y-%m-%dT00:00:00Z"),
},
).json()
# Construct a snapshot URL that will give us binary packages for Ubuntu Bionic (18.04)
@@ -202,11 +201,34 @@ class RBuildPack(PythonBuildPack):
"https://packagemanager.rstudio.com/all/__linux__/bionic/"
+ snapshots["upsi"]
)
+ raise ValueError('No snapshot found for {} or {} days prior in packagemanager.rstudio.com'.format(
+ snapshot_date.strftime("%Y-%m-%d"), max_days_prior
+ ))
+ def get_mran_snapshot_url(self, snapshot_date, max_days_prior=7):
+ for i in range(max_days_prior):
+ try_date = snapshot_date - datetime.timedelta(days=i)
# Fall back to MRAN if packagemanager.rstudio.com doesn't have it
- return "https://mran.microsoft.com/snapshot/{}".format(
- snapshot_date.isoformat()
+ url = "https://mran.microsoft.com/snapshot/{}".format(
+ try_date.isoformat()
)
+ r = requests.head(url)
+ if r.ok:
+ return url
+ else:
+ raise ValueError('No snapshot found for {} or {} days prior in mran.microsoft.com'.format(
+ snapshot_date.strftime("%Y-%m-%d"), max_days_prior
+ ))
+
+ def get_cran_mirror_url(self, snapshot_date):
+ # Date after which we will use rspm + binary packages instead of MRAN + source packages
+ rspm_cutoff_date = datetime.date(2022, 1, 1)
+
+ if snapshot_date >= rspm_cutoff_date or self.r_version >= V('4.1'):
+ return self.get_rspm_snapshot_url(snapshot_date)
+ else:
+ return self.get_mran_snapshot_url(snapshot_date)
+
def get_devtools_snapshot_date(self):
"""

MODIFY
tests/r/r4.1/runtime.txt
tests/r/r4.1/runtime.txt
-r-4.1-2021-07-07
+r-4.1-2021-10-22

MODIFY
tests/r/r4.1/verify
tests/r/r4.1/verify
#!/usr/bin/env Rscript
library('ggplot2')
-print(version)
# Fail if version is not 4.1
if (!(version$major == "4" && as.double(version$minor) >= 1 && as.double(version$minor) < 2)) {
quit("yes", 1)
}
+
+# R 4.1 always uses rspm snapshots
+if (!(startsWith(options()$repos["CRAN"], "https://packagemanager.rstudio.com"))) {
+ quit("yes", 1)
+}

MODIFY
tests/unit/test_r.py
tests/unit/test_r.py
@@ -64,28 +64,46 @@ def test_mran_date(tmpdir, runtime, expected):
assert r.checkpoint_date == date(*expected)
-@pytest.mark.parametrize("expected", ["2019-12-29", "2019-12-26"])
-def test_mran_latestdate(tmpdir, expected):
+def test_snapshot_rspm_date():
+ test_dates = {
+ # Even though there is no snapshot specified in the interface at https://packagemanager.rstudio.com/client/#/repos/1/overview
+ # For 2021 Oct 22, the API still returns a valid URL that one can install
+ # packages from - probably some server side magic that repeats our client side logic.
+ # No snapshot for this date from
+ date(2021, 10, 22): date(2021, 10, 22),
+ # Snapshot exists for this date
+ date(2022, 1, 1): date(2022, 1, 1),
+ }
+
+ r = buildpacks.RBuildPack()
+ for requested, expected in test_dates.items():
+ snapshot_url = r.get_rspm_snapshot_url(requested)
+ print(snapshot_url)
+ assert snapshot_url.startswith(
+ "https://packagemanager.rstudio.com/all/__linux__/bionic/" + expected.strftime("%Y-%m-%d")
+ )
+
+ with pytest.raises(ValueError):
+ r.get_rspm_snapshot_url(date(1691, 9, 5))
+
+
+@pytest.mark.parametrize("expected", [date(2019, 12, 29), date(2019, 12, 26)])
+@pytest.mark.parametrize("requested", [date(2019, 12, 31)])
+def test_snapshot_mran_date(requested, expected):
def mock_request_head(url):
r = Response()
- if url == "https://mran.microsoft.com/snapshot/" + expected:
+ if url == "https://mran.microsoft.com/snapshot/" + expected.isoformat():
r.status_code = 200
else:
r.status_code = 404
r.reason = "Mock MRAN no snapshot"
return r
- tmpdir.chdir()
-
- with open("DESCRIPTION", "w") as f:
- f.write("")
-
with patch("requests.head", side_effect=mock_request_head):
- with patch("datetime.date") as mockdate:
- mockdate.today.return_value = date(2019, 12, 31)
r = buildpacks.RBuildPack()
- r.detect()
- assert r.checkpoint_date.isoformat() == expected
+ assert r.get_mran_snapshot_url(
+ requested
+ ) == "https://mran.microsoft.com/snapshot/{}".format(expected.isoformat())
def test_install_from_base(tmpdir):

Solidify logic for picking rspm vs mran

- MRAN doesn't seem to have R 4.1 specific snapshots, so let's
  default to RSPM for anything 4.1+.
- Otherwise, snapshot dates in 2022 will result in using rspm