[MRG] Add support for installing different versions of R (#772)

[MRG] Add support for installing different versions of R
pull/797/head
Tim Head 2019-09-18 08:15:11 +02:00 zatwierdzone przez GitHub
commit bfbec349c2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
4 zmienionych plików z 238 dodań i 40 usunięć

Wyświetl plik

@ -212,7 +212,7 @@ If you only need to run things once during the build phase use :ref:`postBuild`.
Sometimes you want to specify the version of the runtime Sometimes you want to specify the version of the runtime
(e.g. the version of Python or R), (e.g. the version of Python or R),
but the environment specification format don't let you specify this information but the environment specification format will not let you specify this information
(e.g. requirements.txt or install.R). (e.g. requirements.txt or install.R).
For these cases, we have a special file, ``runtime.txt``. For these cases, we have a special file, ``runtime.txt``.
@ -220,7 +220,7 @@ For these cases, we have a special file, ``runtime.txt``.
``runtime.txt`` is only supported when used with environment specifications ``runtime.txt`` is only supported when used with environment specifications
that do not already support specifying the runtime that do not already support specifying the runtime
(e.g. when using ``environment.yml`` for conda or ``Project.toml`` for Julia, (when using ``environment.yml`` for conda or ``Project.toml`` for Julia,
``runtime.txt`` will be ignored). ``runtime.txt`` will be ignored).
To use python-2.7: add ``python-2.7`` in runtime.txt file. To use python-2.7: add ``python-2.7`` in runtime.txt file.
@ -231,8 +231,11 @@ Python 2 installed. To see a full example repository, visit our
repo2docker uses R libraries pinned to a specific snapshot on repo2docker uses R libraries pinned to a specific snapshot on
`MRAN <https://mran.microsoft.com/documents/rro/reproducibility>`_. `MRAN <https://mran.microsoft.com/documents/rro/reproducibility>`_.
You need to have a ``runtime.txt`` file that is formatted as You need to have a ``runtime.txt`` file that is formatted as
``r-<YYYY>-<MM>-<DD>``, where YYYY-MM-DD is a snapshot at MRAN that will be ``r-<RVERSION>-<YYYY>-<MM>-<DD>``, where YYYY-MM-DD is a snapshot at MRAN that will be
used for installing libraries. used for installing libraries. You can set RVERSION to 3.4, 3.5 or 3.6 to select
the version of R you want to use. If you do not specify a R version the latest
released version will be used (currently R 3.6). You can also specify the exact
patch release you want to use for the 3.5 and 3.6 series.
To see an example R repository, visit our `R To see an example R repository, visit our `R
example in binder-examples <https://github.com/binder-examples/r/blob/master/runtime.txt>`_. example in binder-examples <https://github.com/binder-examples/r/blob/master/runtime.txt>`_.

Wyświetl plik

@ -49,10 +49,11 @@ Julia versions 0.6.x and earlier are supported via a :ref:`REQUIRE <REQUIRE>` fi
R R
~ ~
Only R 3.4.4 is currently supported, which is installed via ``apt`` from the The default version of R is currently R 3.6.1. You can select the version of
`ubuntu bionic repository <https://packages.ubuntu.com/bionic/r-base>`_. R you want to use by specifying it in the :ref:`runtime.txt <runtime.txt>`
file.
We support R versions 3.4, 3.5 and 3.6.
Why is my repository is failing to build with ``ResolvePackageNotFound`` ? Why is my repository is failing to build with ``ResolvePackageNotFound`` ?

Wyświetl plik

@ -2,6 +2,8 @@ import re
import os import os
import datetime import datetime
from distutils.version import LooseVersion as V
from .python import PythonBuildPack from .python import PythonBuildPack
@ -55,6 +57,41 @@ class RBuildPack(PythonBuildPack):
return self._runtime return self._runtime
@property
def r_version(self):
"""Detect the R version for a given `runtime.txt`
Will return the version specified by the user or the current default
version.
"""
version_map = {
"3.4": "3.4",
"3.5": "3.5.3-1bionic",
"3.5.0": "3.5.0-1bionic",
"3.5.1": "3.5.1-2bionic",
"3.5.2": "3.5.2-1bionic",
"3.5.3": "3.5.3-1bionic",
"3.6": "3.6.1-3bionic",
"3.6.0": "3.6.0-2bionic",
"3.6.1": "3.6.1-3bionic",
}
# the default if nothing is specified
r_version = "3.6"
if not hasattr(self, "_r_version"):
parts = self.runtime.split("-")
if len(parts) == 5:
r_version = parts[1]
if r_version not in version_map:
raise ValueError(
"Version '{}' of R is not supported.".format(r_version)
)
# translate to the full version string
self._r_version = version_map.get(r_version)
return self._r_version
@property @property
def checkpoint_date(self): def checkpoint_date(self):
""" """
@ -63,11 +100,14 @@ class RBuildPack(PythonBuildPack):
Returns '' if no date is specified Returns '' if no date is specified
""" """
if not hasattr(self, "_checkpoint_date"): if not hasattr(self, "_checkpoint_date"):
match = re.match(r"r-(\d\d\d\d)-(\d\d)-(\d\d)", self.runtime) match = re.match(r"r-(\d.\d(.\d)?-)?(\d\d\d\d)-(\d\d)-(\d\d)", self.runtime)
if not match: if not match:
self._checkpoint_date = False self._checkpoint_date = False
else: else:
self._checkpoint_date = datetime.date(*[int(s) for s in match.groups()]) # turn the last three groups of the match into a date
self._checkpoint_date = datetime.date(
*[int(s) for s in match.groups()[-3:]]
)
return self._checkpoint_date return self._checkpoint_date
@ -127,20 +167,19 @@ class RBuildPack(PythonBuildPack):
We install a base version of R, and packages required for RStudio to We install a base version of R, and packages required for RStudio to
be installed. be installed.
""" """
return ( packages = [
super()
.get_packages()
.union(
[
"r-base",
# For rstudio # For rstudio
"psmisc", "psmisc",
"libapparmor1", "libapparmor1",
"sudo", "sudo",
"lsb-release", "lsb-release",
] ]
) # For R 3.4 we use the default Ubuntu package, for other versions we
) # install from a different PPA
if V(self.r_version) < V("3.5"):
packages.append("r-base")
return super().get_packages().union(packages)
def get_build_scripts(self): def get_build_scripts(self):
""" """
@ -180,7 +219,38 @@ class RBuildPack(PythonBuildPack):
self.checkpoint_date.isoformat() self.checkpoint_date.isoformat()
) )
scripts = [ scripts = []
# For R 3.4 we want to use the default Ubuntu package but otherwise
# we use the packages from a PPA
if V(self.r_version) >= V("3.5"):
scripts += [
(
"root",
r"""
echo "deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/" > /etc/apt/sources.list.d/r3.6-ubuntu.list
""",
),
(
"root",
r"""
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
""",
),
(
"root",
r"""
apt-get update && \
apt-get install --yes r-base={} && \
apt-get -qq purge && \
apt-get -qq clean && \
rm -rf /var/lib/apt/lists/*
""".format(
self.r_version
),
),
]
scripts += [
( (
"root", "root",
r""" r"""
@ -277,6 +347,8 @@ class RBuildPack(PythonBuildPack):
] ]
if "r" in self.stencila_contexts: if "r" in self.stencila_contexts:
# new versions of R require a different way of installing bioconductor
if V(self.r_version) <= V("3.5"):
scripts += [ scripts += [
( (
"${NB_USER}", "${NB_USER}",
@ -289,6 +361,19 @@ class RBuildPack(PythonBuildPack):
) )
] ]
else:
scripts += [
(
"${NB_USER}",
# Install and register stencila library
r"""
R --quiet -e "install.packages('BiocManager'); BiocManager::install(); BiocManager::install(c('graph'))" && \
R --quiet -e "devtools::install_github('stencila/r', ref = '361bbf560f3f0561a8612349bca66cd8978f4f24')" && \
R --quiet -e "stencila::register()"
""",
)
]
return super().get_build_scripts() + scripts return super().get_build_scripts() + scripts
def get_preassemble_script_files(self): def get_preassemble_script_files(self):

Wyświetl plik

@ -0,0 +1,109 @@
from datetime import date
import pytest
from repo2docker import buildpacks
def test_unsupported_version(tmpdir):
tmpdir.chdir()
with open("runtime.txt", "w") as f:
f.write("r-3.8-2019-01-01")
r = buildpacks.RBuildPack()
with pytest.raises(ValueError) as excinfo:
# access the property to trigger the exception
_ = r.r_version
# check the version is mentioned in the exception
assert "'3.8'" in str(excinfo.value)
@pytest.mark.parametrize(
"runtime_version, expected", [("", "3.6"), ("3.6", "3.6"), ("3.5.1", "3.5")]
)
def test_version_specification(tmpdir, runtime_version, expected):
tmpdir.chdir()
with open("runtime.txt", "w") as f:
if runtime_version:
runtime_version += "-"
f.write(f"r-{runtime_version}2019-01-01")
r = buildpacks.RBuildPack()
assert r.r_version.startswith(expected)
def test_version_completion(tmpdir):
tmpdir.chdir()
with open("runtime.txt", "w") as f:
f.write(f"r-3.6-2019-01-01")
r = buildpacks.RBuildPack()
assert r.r_version == "3.6.1-3bionic"
@pytest.mark.parametrize(
"runtime, expected",
[
("r-2019-01-01", (2019, 1, 1)),
("r-3.6.1-2019-01-01", (2019, 1, 1)),
("r-3.5-2019-01-01", (2019, 1, 1)),
],
)
def test_mran_date(tmpdir, runtime, expected):
tmpdir.chdir()
with open("runtime.txt", "w") as f:
f.write(runtime)
r = buildpacks.RBuildPack()
assert r.checkpoint_date == date(*expected)
def test_install_from_base(tmpdir):
# check that for R==3.4 we install from ubuntu
tmpdir.chdir()
with open("runtime.txt", "w") as f:
f.write("r-3.4-2019-01-02")
r = buildpacks.RBuildPack()
assert "r-base" in r.get_packages()
def test_install_from_ppa(tmpdir):
# check that for R>3.4 we don't install r-base from Ubuntu
tmpdir.chdir()
with open("runtime.txt", "w") as f:
f.write("r-3.5-2019-01-02")
r = buildpacks.RBuildPack()
assert "r-base" not in r.get_packages()
def test_custom_ppa(tmpdir):
tmpdir.chdir()
with open("runtime.txt", "w") as f:
f.write("r-3.5-2019-01-02")
r = buildpacks.RBuildPack()
scripts = r.get_build_scripts()
# check that at least one of the build scripts adds this new PPA
for user, script in scripts:
if "https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/" in script:
break
else:
assert False, "Should have added a new PPA"
# check that we install the right package
for user, script in scripts:
if "r-base=3.5" in script:
break
else:
assert False, "Should have installed base R"