diff --git a/.travis.yml b/.travis.yml index d691fa2f..01c92c9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ env: - REPO_TYPE=conda - REPO_TYPE=venv - REPO_TYPE=julia + - REPO_TYPE=r - REPO_TYPE=dockerfile - REPO_TYPE=external/* - REPO_TYPE=*.py diff --git a/repo2docker/app.py b/repo2docker/app.py index 21e909c7..cab2e2d5 100644 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -29,7 +29,8 @@ import subprocess from .buildpacks import ( PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack, - CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage + CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage, + RBuildPack ) from .utils import execute_cmd, ByteSpecification, maybe_cleanup, is_valid_docker_image_name, validate_and_generate_port_mapping from . import __version__ @@ -64,6 +65,7 @@ class Repo2Docker(Application): JuliaBuildPack(), CondaBuildPack(), Python2BuildPack(), + RBuildPack(), PythonBuildPack() ], config=True, diff --git a/repo2docker/buildpacks/__init__.py b/repo2docker/buildpacks/__init__.py index 0f54f390..ae60c05a 100644 --- a/repo2docker/buildpacks/__init__.py +++ b/repo2docker/buildpacks/__init__.py @@ -4,3 +4,4 @@ from .conda import CondaBuildPack from .julia import JuliaBuildPack from .docker import DockerBuildPack from .legacy import LegacyBinderDockerBuildPack +from .r import RBuildPack diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py new file mode 100644 index 00000000..b18241d7 --- /dev/null +++ b/repo2docker/buildpacks/r.py @@ -0,0 +1,158 @@ +import re +import os +import datetime + +from .python import PythonBuildPack + +class RBuildPack(PythonBuildPack): + """ + Setup R for use with a repository + + This sets up R + RStudio + IRKernel for a repository that contains: + + 1. A `runtime.txt` file with the text: + + r--- + + Where 'year', 'month' and 'date' refer to a specific + date snapshot of https://mran.microsoft.com/timemachine + from which libraries are to be installed. + + 2. An optional `install.R` file that will be executed at build time, + and can be used for installing packages from both MRAN and GitHub. + + It currently sets up R from the ubuntu repository being used. This + is unideal, and we should investigate other solutions! + """ + @property + def runtime(self): + """ + Return contents of runtime.txt if it exists, '' otherwise + """ + if not hasattr(self, '_runtime'): + runtime_path = self.binder_path('runtime.txt') + try: + with open(runtime_path) as f: + self._runtime = f.read().strip() + except FileNotFoundError: + self._runtime = '' + + return self._runtime + + @property + def checkpoint_date(self): + """ + Return the date of MRAN checkpoint to use for this repo + + Returns '' if no date is specified + """ + if not hasattr(self, '_checkpoint_date'): + match = re.match(r'r-(\d\d\d\d)-(\d\d)-(\d\d)', self.runtime) + if not match: + self._checkpoint_date = False + + year, month, day = [int(s) for s in match.groups()] + + self._checkpoint_date = datetime.date(year, month, day) + + return self._checkpoint_date + + def detect(self): + """ + Check if current repo should be built with the R Build pack + + Note that we explicitly do *not* check if a requirements.txt + is present here (by calling super().detect()). + """ + return bool(self.checkpoint_date) + + def get_path(self): + return super().get_path() + [ + '/usr/lib/rstudio-server/bin/' + ] + + def get_env(self): + return super().get_env() + [ + # This is the path where user libraries are installed + ('R_LIBS_USER', '${APP_BASE}/rlibs') + ] + + def get_packages(self): + return super().get_packages().union([ + 'r-base', + # For rstudio + 'psmisc', + 'libapparmor1', + 'sudo', + 'lsb-release' + ]) + + def get_build_scripts(self): + mran_url = 'https://mran.microsoft.com/snapshot/{}'.format( + self.checkpoint_date.isoformat() + ) + rstudio_url = 'https://download2.rstudio.org/rstudio-server-1.1.419-amd64.deb' + # This is MD5, because that is what RStudio download page provides! + rstudio_checksum = '24cd11f0405d8372b4168fc9956e0386' + return super().get_build_scripts() + [ + ( + "root", + r""" + mkdir -p ${R_LIBS_USER} && \ + chown -R ${NB_USER}:${NB_USER} ${R_LIBS_USER} + """ + ), + ( + "root", + # We set the default CRAN repo to the MRAN one at given date + # We set download method to be curl so we get HTTPS support + r""" + echo "options(repos = c(CRAN='{mran_url}'), download.file.method = 'libcurl')" > /etc/R/Rprofile.site + """.format(mran_url=mran_url) + ), + ( + "root", + # Install RStudio! + r""" + curl -L --fail {rstudio_url} > /tmp/rstudio.deb && \ + echo '{rstudio_checksum} /tmp/rstudio.deb' | md5sum -c - && \ + dpkg -i /tmp/rstudio.deb && \ + rm /tmp/rstudio.deb + """.format( + rstudio_url=rstudio_url, + rstudio_checksum=rstudio_checksum + ) + ), + ( + "${NB_USER}", + # Install a pinned version of IRKernel and set it up for use! + r""" + R --quiet -e "install.packages('devtools')" && \ + R --quiet -e "devtools::install_github('IRkernel/IRkernel', ref='0.8.11')" && \ + R --quiet -e "IRkernel::installspec(prefix='${NB_PYTHON_PREFIX}')" + """ + ), + ( + "${NB_USER}", + # Install nbrsessionproxy + r""" + pip install --no-cache-dir nbrsessionproxy==0.6.1 && \ + jupyter serverextension enable nbrsessionproxy --sys-prefix && \ + jupyter nbextension install --py nbrsessionproxy --sys-prefix && \ + jupyter nbextension enable --py nbrsessionproxy --sys-prefix + """ + ) + + ] + + def get_assemble_scripts(self): + assemble_scripts = super().get_assemble_scripts() + if os.path.exists('install.R'): + assemble_scripts += [ + ( + "${NB_USER}", + "Rscript install.R" + ) + ] + + return assemble_scripts diff --git a/tests/r/simple/install.R b/tests/r/simple/install.R new file mode 100644 index 00000000..df6d6a83 --- /dev/null +++ b/tests/r/simple/install.R @@ -0,0 +1 @@ +install.packages("ggplot2") diff --git a/tests/r/simple/runtime.txt b/tests/r/simple/runtime.txt new file mode 100644 index 00000000..c7960351 --- /dev/null +++ b/tests/r/simple/runtime.txt @@ -0,0 +1 @@ +r-2017-10-24 diff --git a/tests/r/simple/verify b/tests/r/simple/verify new file mode 100755 index 00000000..eec6690f --- /dev/null +++ b/tests/r/simple/verify @@ -0,0 +1,2 @@ +#!/usr/bin/env Rscript +library('ggplot2')