Skip to content

Commit ae8ed29

Browse files
authored
Merge pull request #1161 from yuvipanda/another-r
Get R from RStudio provided apt packages (.deb files)
2 parents 57a95a3 + 8af47e9 commit ae8ed29

File tree

2 files changed

+49
-131
lines changed

2 files changed

+49
-131
lines changed

repo2docker/buildpacks/r.py

Lines changed: 48 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,7 @@ class RBuildPack(PythonBuildPack):
3838
3939
- are needed by a specific tool
4040
41-
The `r-base-core` package from Ubuntu or "Ubuntu packages for R"
42-
apt repositories is used to install R itself,
43-
rather than any of the methods from https://cran.r-project.org/.
44-
45-
The `r-base-dev` package is installed as advised in RStudio instructions.
41+
R is installed from https://docs.rstudio.com/resources/install-r/
4642
"""
4743

4844
@property
@@ -68,33 +64,32 @@ def r_version(self):
6864
version.
6965
"""
7066
version_map = {
71-
"3.4": "3.4",
72-
"3.5": "3.5.3-1bionic",
73-
"3.5.0": "3.5.0-1bionic",
74-
"3.5.1": "3.5.1-2bionic",
75-
"3.5.2": "3.5.2-1bionic",
76-
"3.5.3": "3.5.3-1bionic",
77-
"3.6": "3.6.3-1bionic",
78-
"3.6.0": "3.6.0-2bionic",
79-
"3.6.1": "3.6.1-3bionic",
80-
"4.0": "4.0.5-1.1804.0",
81-
"4.0.2": "4.0.2-1.1804.0",
82-
"4.1": "4.1.2-1.1804.0",
67+
"4.2": "4.2.0",
68+
"4.1": "4.1.3",
69+
"4.0": "4.0.5",
70+
"3.6": "3.6.3",
71+
"3.5": "3.5.3",
72+
"3.4": "3.4.0",
73+
"3.3": "3.3.3",
8374
}
75+
8476
# the default if nothing is specified
85-
r_version = "4.1"
77+
# Use full version is needed here, so it a valid semver
78+
r_version = version_map["4.1"]
8679

8780
if not hasattr(self, "_r_version"):
8881
parts = self.runtime.split("-")
82+
# If runtime.txt is not set, or if it isn't of the form r-<version>-<yyyy>-<mm>-<dd>,
83+
# we don't use any of it in determining r version and just use the default
8984
if len(parts) == 5:
9085
r_version = parts[1]
91-
if r_version not in version_map:
92-
raise ValueError(
93-
"Version '{}' of R is not supported.".format(r_version)
94-
)
86+
# For versions of form x.y, we want to explicitly provide x.y.z - latest patchlevel
87+
# available. Users can however explicitly specify the full version to get something specific
88+
if r_version in version_map:
89+
r_version = version_map[r_version]
9590

9691
# translate to the full version string
97-
self._r_version = version_map.get(r_version)
92+
self._r_version = r_version
9893

9994
return self._r_version
10095

@@ -141,6 +136,20 @@ def detect(self):
141136
self._runtime = "r-{}".format(str(self._checkpoint_date))
142137
return True
143138

139+
def get_env(self):
140+
"""
141+
Set custom env vars needed for RStudio to load
142+
"""
143+
return super().get_env() + [
144+
# rstudio (rsession) can't seem to find R unless we explicitly tell it where
145+
# it is - just $PATH isn't enough. I discovered these are the env vars it
146+
# looks for by digging through RStudio source and finding
147+
# https://github.com/rstudio/rstudio/blob/v2022.02.3+492/src/cpp/r/session/RDiscovery.cpp
148+
("R_HOME", f"/opt/R/{self.r_version}/lib/R"),
149+
("R_DOC_DIR", "${R_HOME}/doc"),
150+
("LD_LIBRARY_PATH", "${R_HOME}/lib:${LD_LIBRARY_PATH}"),
151+
]
152+
144153
def get_path(self):
145154
"""
146155
Return paths to be added to the PATH environment variable.
@@ -177,12 +186,6 @@ def get_packages(self):
177186
"sudo",
178187
"lsb-release",
179188
]
180-
# For R 3.4 we use the default Ubuntu package, for other versions we
181-
# install from a different apt repository
182-
if V(self.r_version) < V("3.5"):
183-
packages.append("r-base")
184-
packages.append("r-base-dev")
185-
packages.append("libclang-dev")
186189

187190
return super().get_packages().union(packages)
188191

@@ -268,46 +271,25 @@ def get_build_scripts(self):
268271

269272
cran_mirror_url = self.get_cran_mirror_url(self.checkpoint_date)
270273

271-
# Determine which R apt repository should be enabled
272-
if V(self.r_version) >= V("3.5"):
273-
if V(self.r_version) >= V("4"):
274-
vs = "40"
275-
else:
276-
vs = "35"
277-
278274
scripts = [
279275
(
280276
"root",
281277
rf"""
282-
echo "deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran{vs}/" > /etc/apt/sources.list.d/r-ubuntu.list
283-
""",
284-
),
285-
# Dont use apt-key directly, as gpg does not always respect *_proxy vars. This increase the chances
286-
# of being able to reach it from behind a firewall
287-
(
288-
"root",
289-
r"""
290-
wget --quiet -O - 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xe298a3a825c0d65dfd57cbb651716619e084dab9' | apt-key add -
291-
""",
292-
),
293-
(
294-
"root",
295-
# we should have --no-install-recommends on all our apt-get install commands,
296-
# but here it's important because it will pull in CRAN packages
297-
# via r-recommends, which is only guaranteed to be compatible with the latest r-base-core
298-
r"""
299278
apt-get update > /dev/null && \
300279
apt-get install --yes --no-install-recommends \
301-
r-base-core={R_version} \
302-
r-base-dev={R_version} \
303280
libclang-dev \
304281
libzmq3-dev > /dev/null && \
282+
wget --quiet -O /tmp/r-{self.r_version}.deb \
283+
https://cdn.rstudio.com/r/ubuntu-$(. /etc/os-release && echo $VERSION_ID | sed 's/\.//')/pkgs/r-{self.r_version}_1_amd64.deb && \
284+
apt install --yes --no-install-recommends /tmp/r-{self.r_version}.deb > /dev/null && \
285+
rm /tmp/r-{self.r_version}.deb && \
305286
apt-get -qq purge && \
306287
apt-get -qq clean && \
307-
rm -rf /var/lib/apt/lists/*
308-
""".format(
309-
R_version=self.r_version
310-
),
288+
rm -rf /var/lib/apt/lists/* && \
289+
ln -s /opt/R/{self.r_version}/bin/R /usr/local/bin/R && \
290+
ln -s /opt/R/{self.r_version}/bin/Rscript /usr/local/bin/Rscript && \
291+
R --version
292+
""",
311293
),
312294
]
313295

@@ -326,9 +308,9 @@ def get_build_scripts(self):
326308
# Set paths so that RStudio shares libraries with base R
327309
# install. This first comments out any R_LIBS_USER that
328310
# might be set in /etc/R/Renviron and then sets it.
329-
r"""
330-
sed -i -e '/^R_LIBS_USER=/s/^/#/' /etc/R/Renviron && \
331-
echo "R_LIBS_USER=${R_LIBS_USER}" >> /etc/R/Renviron
311+
rf"""
312+
sed -i -e '/^R_LIBS_USER=/s/^/#/' /opt/R/{self.r_version}/lib/R/etc/Renviron && \
313+
echo "R_LIBS_USER=${{R_LIBS_USER}}" >> /opt/R/{self.r_version}/lib/R/etc/Renviron
332314
""",
333315
),
334316
(
@@ -338,15 +320,12 @@ def get_build_scripts(self):
338320
# Quite hilarious, IMO.
339321
# See https://docs.rstudio.com/rspm/1.0.12/admin/binaries.html
340322
# Set mirror for RStudio too, by modifying rsession.conf
341-
r"""
323+
rf"""
342324
R RHOME && \
343-
mkdir -p /usr/lib/R/etc /etc/rstudio && \
344-
echo 'options(repos = c(CRAN = "{cran_mirror_url}"))' > /usr/lib/R/etc/Rprofile.site && \
345-
echo 'options(HTTPUserAgent = sprintf("R/%s R (%s)", getRversion(), paste(getRversion(), R.version$platform, R.version$arch, R.version$os)))' >> /usr/lib/R/etc/Rprofile.site && \
325+
mkdir -p /etc/rstudio && \
326+
echo 'options(repos = c(CRAN = "{cran_mirror_url}"))' > /opt/R/{self.r_version}/lib/R/etc/Rprofile.site && \
346327
echo 'r-cran-repos={cran_mirror_url}' > /etc/rstudio/rsession.conf
347-
""".format(
348-
cran_mirror_url=cran_mirror_url
349-
),
328+
""",
350329
),
351330
(
352331
"${NB_USER}",

tests/unit/test_r.py

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,6 @@
77
from repo2docker import buildpacks
88

99

10-
def test_unsupported_version(tmpdir):
11-
tmpdir.chdir()
12-
13-
with open("runtime.txt", "w") as f:
14-
f.write("r-3.8-2019-01-01")
15-
16-
r = buildpacks.RBuildPack()
17-
with pytest.raises(ValueError) as excinfo:
18-
# access the property to trigger the exception
19-
_ = r.r_version
20-
# check the version is mentioned in the exception
21-
assert "'3.8'" in str(excinfo.value)
22-
23-
2410
@pytest.mark.parametrize(
2511
"runtime_version, expected", [("", "4.1"), ("3.6", "3.6"), ("3.5.1", "3.5")]
2612
)
@@ -43,7 +29,7 @@ def test_version_completion(tmpdir):
4329
f.write(f"r-3.6-2019-01-01")
4430

4531
r = buildpacks.RBuildPack()
46-
assert r.r_version == "3.6.3-1bionic"
32+
assert r.r_version == "3.6.3"
4733

4834

4935
@pytest.mark.parametrize(
@@ -104,50 +90,3 @@ def mock_request_head(url):
10490
assert r.get_mran_snapshot_url(
10591
requested
10692
) == "https://mran.microsoft.com/snapshot/{}".format(expected.isoformat())
107-
108-
109-
def test_install_from_base(tmpdir):
110-
# check that for R==3.4 we install from ubuntu
111-
tmpdir.chdir()
112-
113-
with open("runtime.txt", "w") as f:
114-
f.write("r-3.4-2019-01-02")
115-
116-
r = buildpacks.RBuildPack()
117-
assert "r-base" in r.get_packages()
118-
119-
120-
def test_install_from_cran_apt_repo(tmpdir):
121-
# check that for R>3.4 we don't install r-base from Ubuntu
122-
tmpdir.chdir()
123-
124-
with open("runtime.txt", "w") as f:
125-
f.write("r-3.5-2019-01-02")
126-
127-
r = buildpacks.RBuildPack()
128-
assert "r-base" not in r.get_packages()
129-
130-
131-
def test_custom_cran_apt_repo(tmpdir):
132-
tmpdir.chdir()
133-
134-
with open("runtime.txt", "w") as f:
135-
f.write("r-3.5-2019-01-02")
136-
137-
r = buildpacks.RBuildPack()
138-
139-
scripts = r.get_build_scripts()
140-
141-
# check that at least one of the build scripts adds this new apt repository
142-
for user, script in scripts:
143-
if "https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/" in script:
144-
break
145-
else:
146-
assert False, "Should have added a new apt repository"
147-
148-
# check that we install the right package
149-
for user, script in scripts:
150-
if "r-base-core=3.5" in script:
151-
break
152-
else:
153-
assert False, "Should have installed base R"

0 commit comments

Comments
 (0)