diff --git a/repo2docker/buildpacks/_r_base.py b/repo2docker/buildpacks/_r_base.py new file mode 100644 index 00000000..8c2bbac1 --- /dev/null +++ b/repo2docker/buildpacks/_r_base.py @@ -0,0 +1,76 @@ +""" +Base information for using R in BuildPacks. + +Keeping this in r.py would lead to cyclic imports. +""" + +# Via https://rstudio.com/products/rstudio/download-server/debian-ubuntu/ +RSTUDIO_URL = "https://download2.rstudio.org/server/bionic/amd64/rstudio-server-1.2.5001-amd64.deb" +# This is MD5, because that is what RStudio download page provides! +RSTUDIO_CHECKSUM = "d33881b9ab786c09556c410e7dc477de" + +# Via https://www.rstudio.com/products/shiny/download-server/ +SHINY_URL = "https://download3.rstudio.org/ubuntu-14.04/x86_64/shiny-server-1.5.12.933-amd64.deb" +SHINY_CHECKSUM = "9aeef6613e7f58f21c97a4600921340e" + +# Version of MRAN to pull devtools from. +DEVTOOLS_VERSION = "2018-02-01" + +# IRKernel version - specified as a tag in the IRKernel repository +IRKERNEL_VERSION = "1.0.2" + + +def rstudio_base_scripts(): + """Base steps to install RStudio and shiny-server.""" + return [ + ( + "root", + # Install RStudio! + r""" + curl --silent --location --fail {rstudio_url} > /tmp/rstudio.deb && \ + echo '{rstudio_checksum} /tmp/rstudio.deb' | md5sum -c - && \ + apt-get update && \ + apt install -y /tmp/rstudio.deb && \ + rm /tmp/rstudio.deb && \ + apt-get -qq purge && \ + apt-get -qq clean && \ + rm -rf /var/lib/apt/lists/* + """.format( + rstudio_url=RSTUDIO_URL, rstudio_checksum=RSTUDIO_CHECKSUM + ), + ), + ( + "root", + # Install Shiny Server! + r""" + curl --silent --location --fail {url} > {deb} && \ + echo '{checksum} {deb}' | md5sum -c - && \ + dpkg -i {deb} && \ + rm {deb} + """.format( + url=SHINY_URL, checksum=SHINY_CHECKSUM, deb="/tmp/shiny.deb" + ), + ), + ( + "${NB_USER}", + # Install nbrsessionproxy + r""" + pip install --no-cache-dir https://github.com/jupyterhub/jupyter-server-proxy/archive/7ac0125.zip && \ + pip install --no-cache-dir jupyter-rsession-proxy==1.0b6 && \ + jupyter serverextension enable jupyter_server_proxy --sys-prefix && \ + jupyter nbextension install --py jupyter_server_proxy --sys-prefix && \ + jupyter nbextension enable --py jupyter_server_proxy --sys-prefix + """, + ), + ( + # Not all of these locations are configurable; so we make sure + # they exist and have the correct permissions + "root", + r""" + install -o ${NB_USER} -g ${NB_USER} -d /var/log/shiny-server && \ + install -o ${NB_USER} -g ${NB_USER} -d /var/lib/shiny-server && \ + install -o ${NB_USER} -g ${NB_USER} /dev/null /var/log/shiny-server.log && \ + install -o ${NB_USER} -g ${NB_USER} /dev/null /var/run/shiny-server.pid + """, + ), + ] diff --git a/repo2docker/buildpacks/conda/__init__.py b/repo2docker/buildpacks/conda/__init__.py index c781f00a..ec8029fd 100644 --- a/repo2docker/buildpacks/conda/__init__.py +++ b/repo2docker/buildpacks/conda/__init__.py @@ -6,10 +6,12 @@ from collections import Mapping from ruamel.yaml import YAML from ..base import BaseImage +from .._r_base import rstudio_base_scripts, IRKERNEL_VERSION from ...utils import is_local_pip_requirement # pattern for parsing conda dependency line PYTHON_REGEX = re.compile(r"python\s*=+\s*([\d\.]*)") +R_REGEX = re.compile(r"r-base\s*=+\s*([\d\.]*)") # current directory HERE = os.path.dirname(os.path.abspath(__file__)) @@ -179,6 +181,7 @@ class CondaBuildPack(BaseImage): Will return 'x.y' if version is found (e.g '3.6'), or a Falsy empty string '' if not found. + Version information below the minor level is dropped. """ if not hasattr(self, "_python_version"): py_version = None @@ -204,6 +207,40 @@ class CondaBuildPack(BaseImage): return self._python_version + @property + def r_version(self): + """Detect the Python version for a given `environment.yml` + + Will return 'x.y' if version is found (e.g '3.6'), + or a Falsy empty string '' if not found. + + """ + if not hasattr(self, "_r_version"): + self._r_version = "" + env = self.environment_yaml + for dep in env.get("dependencies", []): + if not isinstance(dep, str): + continue + match = R_REGEX.match(dep) + if not match: + continue + self._r_version = match.group(1) + break + + return self._r_version + + @property + def uses_r(self): + """Detect whether the user also installs R packages. + + Will return True when a package prefixed with 'r-' is being installed. + """ + if not hasattr(self, "_uses_r"): + deps = self.environment_yaml.get("dependencies", []) + self._uses_r = any(dep.startswith("r-") for dep in deps) + + return self._uses_r + @property def py2(self): """Am I building a Python 2 kernel environment?""" @@ -241,6 +278,46 @@ class CondaBuildPack(BaseImage): ), ) ) + + if self.uses_r: + if self.r_version: + r_pin = "=" + self.r_version + else: + r_pin = "" + scripts.append( + ( + "${NB_USER}", + r""" + conda install -p {0} r-base{1} r-irkernel={2} r-devtools && \ + conda clean --all -f -y && \ + conda list -p {0} + """.format( + env_prefix, r_pin, IRKERNEL_VERSION + ), + ) + ) + scripts += rstudio_base_scripts() + scripts += [ + ( + "root", + r""" + echo auth-none=1 >> /etc/rstudio/rserver.conf && \ + echo auth-minimum-user-id=0 >> /etc/rstudio/rserver.conf && \ + echo "rsession-which-r={0}/bin/R" >> /etc/rstudio/rserver.conf + """.format( + env_prefix + ), + ), + ( + "${NB_USER}", + # Install a pinned version of IRKernel and set it up for use! + r""" + R --quiet -e "IRkernel::installspec(prefix='{0}')" + """.format( + env_prefix + ), + ), + ] return scripts def get_preassemble_scripts(self): diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 3f0ff5e2..826db18c 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -5,6 +5,7 @@ import datetime from distutils.version import LooseVersion as V from .python import PythonBuildPack +from ._r_base import rstudio_base_scripts, DEVTOOLS_VERSION, IRKERNEL_VERSION class RBuildPack(PythonBuildPack): @@ -206,21 +207,6 @@ class RBuildPack(PythonBuildPack): contents of runtime.txt. """ - # Via https://rstudio.com/products/rstudio/download-server/debian-ubuntu/ - rstudio_url = "https://download2.rstudio.org/server/bionic/amd64/rstudio-server-1.2.5001-amd64.deb" - # This is MD5, because that is what RStudio download page provides! - rstudio_checksum = "d33881b9ab786c09556c410e7dc477de" - - # Via https://www.rstudio.com/products/shiny/download-server/ - shiny_url = "https://download3.rstudio.org/ubuntu-14.04/x86_64/shiny-server-1.5.12.933-amd64.deb" - shiny_checksum = "9aeef6613e7f58f21c97a4600921340e" - - # Version of MRAN to pull devtools from. - devtools_version = "2018-02-01" - - # IRKernel version - specified as a tag in the IRKernel repository - irkernel_version = "1.0.2" - mran_url = "https://mran.microsoft.com/snapshot/{}".format( self.checkpoint_date.isoformat() ) @@ -261,38 +247,17 @@ class RBuildPack(PythonBuildPack): ), ] - scripts += [ + scripts.append( ( "root", r""" mkdir -p ${R_LIBS_USER} && \ chown -R ${NB_USER}:${NB_USER} ${R_LIBS_USER} """, - ), - ( - "root", - # Install RStudio! - r""" - curl --silent --location --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 - ), - ), - ( - "root", - # Install Shiny Server! - r""" - curl --silent --location --fail {url} > {deb} && \ - echo '{checksum} {deb}' | md5sum -c - && \ - dpkg -i {deb} && \ - rm {deb} - """.format( - url=shiny_url, checksum=shiny_checksum, deb="/tmp/shiny.deb" - ), - ), + ) + ) + scripts += rstudio_base_scripts() + scripts += [ ( "root", # Set paths so that RStudio shares libraries with base R @@ -303,17 +268,6 @@ class RBuildPack(PythonBuildPack): echo "R_LIBS_USER=${R_LIBS_USER}" >> /etc/R/Renviron """, ), - ( - "${NB_USER}", - # Install nbrsessionproxy - r""" - pip install --no-cache-dir https://github.com/jupyterhub/jupyter-server-proxy/archive/7ac0125.zip && \ - pip install --no-cache-dir jupyter-rsession-proxy==1.0b6 && \ - jupyter serverextension enable jupyter_server_proxy --sys-prefix && \ - jupyter nbextension install --py jupyter_server_proxy --sys-prefix && \ - jupyter nbextension enable --py jupyter_server_proxy --sys-prefix - """, - ), ( "${NB_USER}", # Install a pinned version of IRKernel and set it up for use! @@ -322,7 +276,7 @@ class RBuildPack(PythonBuildPack): R --quiet -e "devtools::install_github('IRkernel/IRkernel', ref='{irkernel_version}')" && \ R --quiet -e "IRkernel::installspec(prefix='$NB_PYTHON_PREFIX')" """.format( - devtools_version=devtools_version, irkernel_version=irkernel_version + devtools_version=DEVTOOLS_VERSION, irkernel_version=IRKERNEL_VERSION ), ), ( @@ -344,17 +298,6 @@ class RBuildPack(PythonBuildPack): mran_url=mran_url ), ), - ( - # Not all of these locations are configurable; so we make sure - # they exist and have the correct permissions - "root", - r""" - install -o ${NB_USER} -g ${NB_USER} -d /var/log/shiny-server && \ - install -o ${NB_USER} -g ${NB_USER} -d /var/lib/shiny-server && \ - install -o ${NB_USER} -g ${NB_USER} /dev/null /var/log/shiny-server.log && \ - install -o ${NB_USER} -g ${NB_USER} /dev/null /var/run/shiny-server.pid - """, - ), ] if "r" in self.stencila_contexts: