From 7fab2b2d7a4ffe3c33ff009fd4299805e256ee83 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 9 Mar 2026 10:58:11 -0700 Subject: [PATCH 01/10] fix import of universaldetector import from public api, not implementation module --- repo2docker/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo2docker/utils.py b/repo2docker/utils.py index 9c2769e1..1798ff85 100644 --- a/repo2docker/utils.py +++ b/repo2docker/utils.py @@ -96,7 +96,7 @@ def open_guess_encoding(path): Open a file in text mode, specifying its encoding, that we guess using chardet. """ - detector = chardet.universaldetector.UniversalDetector() + detector = chardet.UniversalDetector() with open(path, "rb") as f: for line in f.readlines(): detector.feed(line) From 970459ee004227cdde38c8aec091f33851a157cd Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 9 Mar 2026 11:10:59 -0700 Subject: [PATCH 02/10] actually, let's avoid chardet altogether --- repo2docker/utils.py | 16 ++++------------ setup.py | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/repo2docker/utils.py b/repo2docker/utils.py index 1798ff85..05ba1e53 100644 --- a/repo2docker/utils.py +++ b/repo2docker/utils.py @@ -8,7 +8,7 @@ from functools import partial from shutil import copy2, copystat -import chardet +import charset_normalizer from traitlets import Integer, TraitError @@ -94,21 +94,13 @@ def chdir(path): def open_guess_encoding(path): """ Open a file in text mode, specifying its encoding, - that we guess using chardet. + that we guess using charset_normalizer. """ - detector = chardet.UniversalDetector() with open(path, "rb") as f: - for line in f.readlines(): - detector.feed(line) - if detector.done: - break - detector.close() + detector = charset_normalizer.from_fp(f) - file = open(path, encoding=detector.result["encoding"]) - try: + with open(path, encoding=detector.best().encoding) as file: yield file - finally: - file.close() def validate_and_generate_port_mapping(port_mappings): diff --git a/setup.py b/setup.py index d629d2e2..754dc59b 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def get_identifier(json): name="jupyter-repo2docker", version=versioneer.get_version(), install_requires=[ - "chardet", + "charset-normalizer", "docker!=5.0.0", "entrypoints", "escapism", From 77de89888d72fb98e1a581a1f19424ce133d4c52 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 17 Apr 2026 14:29:08 -0700 Subject: [PATCH 03/10] fix figshare url regex software is a valid category this is fragile and other categories can also fail --- repo2docker/contentproviders/figshare.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/repo2docker/contentproviders/figshare.py b/repo2docker/contentproviders/figshare.py index 6c7b26c8..c2331b78 100644 --- a/repo2docker/contentproviders/figshare.py +++ b/repo2docker/contentproviders/figshare.py @@ -41,7 +41,9 @@ def __init__(self): # We may need to add other item types in future, see # https://github.com/jupyterhub/repo2docker/pull/1001#issuecomment-760107436 # for a list - url_regex = re.compile(r"(.*)/articles/(code/|dataset/)?([^/]+)/(\d+)(/)?(\d+)?") + url_regex = re.compile( + r"(.*)/articles/(code/|dataset/|software/)?([^/]+)/(\d+)(/)?(\d+)?" + ) def detect(self, doi, ref=None, extra_args=None): """Trigger this provider for things that resolve to a Figshare article""" From beec52f5bd314c1ef27c4faa305c33f9f384d432 Mon Sep 17 00:00:00 2001 From: Raniere Gaia Costa da Silva Date: Wed, 9 Jul 2025 17:12:54 +0200 Subject: [PATCH 04/10] Add --vanilla to R call Closes https://github.com/jupyterhub/repo2docker/issues/1431 --- repo2docker/buildpacks/r.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index ddc18ed3..e6b022a7 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -353,8 +353,8 @@ def get_build_scripts(self): # Install a pinned version of devtools, IRKernel and shiny rf""" export EXPANDED_CRAN_MIRROR_URL="$(. /etc/os-release && echo {cran_mirror_url} | envsubst)" && \ - R --quiet -e "install.packages(c('devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ - R --quiet -e "IRkernel::installspec(prefix=Sys.getenv(\"NB_PYTHON_PREFIX\"))" + R --vanilla --quiet -e "install.packages(c('devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ + R --vanilla --quiet -e "IRkernel::installspec(prefix=Sys.getenv(\"NB_PYTHON_PREFIX\"))" """, ), ] @@ -390,7 +390,7 @@ def get_preassemble_scripts(self): "${NB_USER}", # Delete /tmp/downloaded_packages only if install.R fails, as the second # invocation of install.R might be able to reuse them - f"Rscript {installR_path} && touch /tmp/.preassembled || true && rm -rf /tmp/downloaded_packages", + f"Rscript --vanilla {installR_path} && touch /tmp/.preassembled || true && rm -rf /tmp/downloaded_packages", ) ] @@ -408,14 +408,14 @@ def get_assemble_scripts(self): "${NB_USER}", # only run install.R if the pre-assembly failed # Delete any downloaded packages in /tmp, as they aren't reused by R - f"""if [ ! -f /tmp/.preassembled ]; then Rscript {installR_path}; rm -rf /tmp/downloaded_packages; fi""", + f"""if [ ! -f /tmp/.preassembled ]; then Rscript --vanilla {installR_path}; rm -rf /tmp/downloaded_packages; fi""", ) ] description_R = "DESCRIPTION" if not self.binder_dir and os.path.exists(description_R): assemble_scripts += [ - ("${NB_USER}", 'R --quiet -e "devtools::install_local(getwd())"') + ("${NB_USER}", 'R --vanilla --quiet -e "devtools::install_local(getwd())"') ] return assemble_scripts From 1a941b8e2fd344aa7c6331730787e22610ad6f00 Mon Sep 17 00:00:00 2001 From: Raniere Gaia Costa da Silva Date: Thu, 21 Aug 2025 17:21:59 +0200 Subject: [PATCH 05/10] Replace --vanilla with --no-init-file Closes https://github.com/jupyterhub/repo2docker/issues/1450 --- repo2docker/buildpacks/r.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index e6b022a7..9b0f7376 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -353,8 +353,8 @@ def get_build_scripts(self): # Install a pinned version of devtools, IRKernel and shiny rf""" export EXPANDED_CRAN_MIRROR_URL="$(. /etc/os-release && echo {cran_mirror_url} | envsubst)" && \ - R --vanilla --quiet -e "install.packages(c('devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ - R --vanilla --quiet -e "IRkernel::installspec(prefix=Sys.getenv(\"NB_PYTHON_PREFIX\"))" + R --no-save --no-restore --no-init-file --no-environ --quiet -e "install.packages(c('devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ + R --no-save --no-restore --no-init-file --no-environ --quiet -e "IRkernel::installspec(prefix=Sys.getenv(\"NB_PYTHON_PREFIX\"))" """, ), ] @@ -390,7 +390,7 @@ def get_preassemble_scripts(self): "${NB_USER}", # Delete /tmp/downloaded_packages only if install.R fails, as the second # invocation of install.R might be able to reuse them - f"Rscript --vanilla {installR_path} && touch /tmp/.preassembled || true && rm -rf /tmp/downloaded_packages", + f"Rscript --no-save --no-restore --no-init-file --no-environ {installR_path} && touch /tmp/.preassembled || true && rm -rf /tmp/downloaded_packages", ) ] @@ -408,14 +408,17 @@ def get_assemble_scripts(self): "${NB_USER}", # only run install.R if the pre-assembly failed # Delete any downloaded packages in /tmp, as they aren't reused by R - f"""if [ ! -f /tmp/.preassembled ]; then Rscript --vanilla {installR_path}; rm -rf /tmp/downloaded_packages; fi""", + f"""if [ ! -f /tmp/.preassembled ]; then Rscript --no-save --no-restore --no-init-file --no-environ {installR_path}; rm -rf /tmp/downloaded_packages; fi""", ) ] description_R = "DESCRIPTION" if not self.binder_dir and os.path.exists(description_R): assemble_scripts += [ - ("${NB_USER}", 'R --vanilla --quiet -e "devtools::install_local(getwd())"') + ( + "${NB_USER}", + 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "devtools::install_local(getwd())"', + ) ] return assemble_scripts From 617ba9b4afe6e49e132232fc5416a7f16773a9ec Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 17 Apr 2026 16:15:35 -0700 Subject: [PATCH 06/10] R: install with pak devtools::install_local has been deprecated --- repo2docker/buildpacks/r.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 9b0f7376..f98c2395 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -417,7 +417,7 @@ def get_assemble_scripts(self): assemble_scripts += [ ( "${NB_USER}", - 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "devtools::install_local(getwd())"', + 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "pak::local_install()"', ) ] From dac166c030bc5a93c2f7847f6b3690fa54b0f626 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 17 Apr 2026 16:57:28 -0700 Subject: [PATCH 07/10] clear cache after pak install can't seem to _prevent_ writing cache --- repo2docker/buildpacks/r.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index f98c2395..00a712e6 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -417,7 +417,7 @@ def get_assemble_scripts(self): assemble_scripts += [ ( "${NB_USER}", - 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "pak::local_install()"', + 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "pak::local_install()" && rm -rf $HOME/.cache/R', ) ] From ccb7a45e1d3ed9db893b21428cd89f6a313f245a Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 17 Apr 2026 17:38:39 -0700 Subject: [PATCH 08/10] manually cleanup after pak it leaves files in /tmp/ and ~/.cache/R/pkgcache/ --- repo2docker/buildpacks/r.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 00a712e6..0e1c88cf 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -350,10 +350,10 @@ def get_build_scripts(self): ), ( "${NB_USER}", - # Install a pinned version of devtools, IRKernel and shiny + # Install a pinned version of pak, devtools, IRKernel and shiny rf""" export EXPANDED_CRAN_MIRROR_URL="$(. /etc/os-release && echo {cran_mirror_url} | envsubst)" && \ - R --no-save --no-restore --no-init-file --no-environ --quiet -e "install.packages(c('devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ + R --no-save --no-restore --no-init-file --no-environ --quiet -e "install.packages(c('pak', 'devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ R --no-save --no-restore --no-init-file --no-environ --quiet -e "IRkernel::installspec(prefix=Sys.getenv(\"NB_PYTHON_PREFIX\"))" """, ), @@ -417,7 +417,10 @@ def get_assemble_scripts(self): assemble_scripts += [ ( "${NB_USER}", - 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "pak::local_install()" && rm -rf $HOME/.cache/R', + # pak doesn't seem to cleanup after itself. + # pak::cache_clean() doesn't empty the cache, + # it leaves files in /tmp/Rabc123.../ and ~/.cache/R/pkg/... + 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "pak::install_local()"; rm -rf $HOME/.cache/R; rm -rf /tmp/R*', ) ] From 0d2a63e714a953413ba0c505a0f71d5fae0bad6c Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 17 Apr 2026 20:22:52 -0700 Subject: [PATCH 09/10] remotes doesn't leave a mess pak doesn't work if caches are cleaned --- repo2docker/buildpacks/r.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 0e1c88cf..4c77e591 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -350,10 +350,10 @@ def get_build_scripts(self): ), ( "${NB_USER}", - # Install a pinned version of pak, devtools, IRKernel and shiny + # Install a pinned version of remotes, devtools, IRKernel and shiny rf""" export EXPANDED_CRAN_MIRROR_URL="$(. /etc/os-release && echo {cran_mirror_url} | envsubst)" && \ - R --no-save --no-restore --no-init-file --no-environ --quiet -e "install.packages(c('pak', 'devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ + R --no-save --no-restore --no-init-file --no-environ --quiet -e "install.packages(c('remotes', 'devtools', 'IRkernel', 'shiny'), repos=Sys.getenv(\"EXPANDED_CRAN_MIRROR_URL\"))" && \ R --no-save --no-restore --no-init-file --no-environ --quiet -e "IRkernel::installspec(prefix=Sys.getenv(\"NB_PYTHON_PREFIX\"))" """, ), @@ -417,10 +417,7 @@ def get_assemble_scripts(self): assemble_scripts += [ ( "${NB_USER}", - # pak doesn't seem to cleanup after itself. - # pak::cache_clean() doesn't empty the cache, - # it leaves files in /tmp/Rabc123.../ and ~/.cache/R/pkg/... - 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "pak::install_local()"; rm -rf $HOME/.cache/R; rm -rf /tmp/R*', + 'R --no-save --no-restore --no-init-file --no-environ --quiet -e "remotes::install_local()"', ) ] From 5dd83791d3e3c34a58b4954d672bf413f5da4632 Mon Sep 17 00:00:00 2001 From: yacchin1205 <968739+yacchin1205@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:58:56 +0900 Subject: [PATCH 10/10] Install CS-rstudio-grdm from GitHub archive tarball to avoid API rate limits Co-Authored-By: Claude Fable 5 --- repo2docker/buildpacks/conda/__init__.py | 57 +++++++++++++++++------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/repo2docker/buildpacks/conda/__init__.py b/repo2docker/buildpacks/conda/__init__.py index 7ec8eda5..e277cc40 100644 --- a/repo2docker/buildpacks/conda/__init__.py +++ b/repo2docker/buildpacks/conda/__init__.py @@ -1,4 +1,5 @@ """BuildPack for conda environments""" + import os import re import warnings @@ -13,10 +14,10 @@ from .._r_base import rstudio_base_scripts from ..base import BaseImage from .matlab import ( - matlab_requirements_scripts, matlab_installation_scripts, - matlab_python_engine_installation_scripts, matlab_proxy_installation_scripts, + matlab_python_engine_installation_scripts, + matlab_requirements_scripts, ) # pattern for parsing conda dependency line @@ -483,7 +484,7 @@ def _get_assemble_scripts_for_r(self): installR_path = self.binder_path("install.R") if not os.path.exists(installR_path): return [] - repo_url = 'https://cloud.r-project.org/' + repo_url = "https://cloud.r-project.org/" return [ ( "${NB_USER}", @@ -512,8 +513,12 @@ def _get_assemble_scripts_for_matlab(self): raise ValueError("mpm.yml must contain a 'release' field") scripts = matlab_requirements_scripts(config["release"], self.base_image) matlab_dir = "/opt/matlab" - scripts += matlab_installation_scripts(config["release"], config.get("products", []), matlab_dir) - scripts += matlab_python_engine_installation_scripts(config["release"], matlab_dir) + scripts += matlab_installation_scripts( + config["release"], config.get("products", []), matlab_dir + ) + scripts += matlab_python_engine_installation_scripts( + config["release"], matlab_dir + ) scripts += matlab_proxy_installation_scripts() return scripts @@ -524,18 +529,23 @@ def _get_env_for_matlab(self): return [("MW_CONTEXT_TAGS", "MATLAB_PROXY:JUPYTER:MPM:V1")] def _get_jlab_extension_script( - self, grdm_jlab_release_tag, grdm_jlab_filename_body, jupyter_resource_usage_tag, + self, + grdm_jlab_release_tag, + grdm_jlab_filename_body, + jupyter_resource_usage_tag, perform_labextension_install=True, perform_nbextension_install=True, perform_jlpm_cache_clean=True, perform_npm_cache_clean=True, ): - grdm_jlab_release_url = ( - f"https://github.com/RCOSDP/CS-jupyterlab-grdm/releases/download/{grdm_jlab_release_tag}" + grdm_jlab_release_url = f"https://github.com/RCOSDP/CS-jupyterlab-grdm/releases/download/{grdm_jlab_release_tag}" + jupyter_resource_usage_release_url = ( + "https://github.com/RCOSDP/CS-jupyter-resource-usage" ) - jupyter_resource_usage_release_url = "https://github.com/RCOSDP/CS-jupyter-resource-usage" grdm_jlab_filename_tar_gz = f"{grdm_jlab_filename_body}.tar.gz" - grdm_jlab_filename_tgz = "{}.tgz".format(grdm_jlab_filename_body.replace("_", "-")) + grdm_jlab_filename_tgz = "{}.tgz".format( + grdm_jlab_filename_body.replace("_", "-") + ) jlpm_cache_clean = "" if perform_jlpm_cache_clean: jlpm_cache_clean = "jlpm cache clean" @@ -578,16 +588,24 @@ def _get_jlab_extension_script( def get_custom_extension_script(self, post): grdm_jlab3_release_tag = "0.2.0" - grdm_jlab3_filename_body = f"rdm_binderhub_jlabextension-refs.tags.{grdm_jlab3_release_tag}" + grdm_jlab3_filename_body = ( + f"rdm_binderhub_jlabextension-refs.tags.{grdm_jlab3_release_tag}" + ) grdm_jlab4_release_tag = "0.3.0" - grdm_jlab4_filename_body = f"rdm_binderhub_jlabextension-{grdm_jlab4_release_tag}" + grdm_jlab4_filename_body = ( + f"rdm_binderhub_jlabextension-{grdm_jlab4_release_tag}" + ) jlab3_ext_script = self._get_jlab_extension_script( - grdm_jlab3_release_tag, grdm_jlab3_filename_body, 'v2023.07', + grdm_jlab3_release_tag, + grdm_jlab3_filename_body, + "v2023.07", ) jlab4_ext_script = self._get_jlab_extension_script( - grdm_jlab4_release_tag, grdm_jlab4_filename_body, 'v2024.04', + grdm_jlab4_release_tag, + grdm_jlab4_filename_body, + "v2024.04", perform_labextension_install=False, perform_nbextension_install=False, perform_jlpm_cache_clean=False, @@ -595,9 +613,14 @@ def get_custom_extension_script(self, post): ) if post: - install_grdm = 'options(repos = c(CRAN = \\"https://cloud.r-project.org/\\")); ' \ - + 'if (!(\\"remotes\\" %in% rownames(installed.packages()))) install.packages(\\"remotes\\"); ' \ - + 'remotes::install_github(\\"RCOSDP/CS-rstudio-grdm\\", type = \\"source\\")' + # install from the GitHub archive tarball; install_github queries + # api.github.com, which is rate-limited for unauthenticated builds + install_grdm = ( + 'options(repos = c(CRAN = \\"https://cloud.r-project.org/\\")); ' + + 'if (!(\\"remotes\\" %in% rownames(installed.packages()))) install.packages(\\"remotes\\"); ' + + 'remotes::install_url(\\"https://github.com/RCOSDP/CS-rstudio-grdm/archive/refs/heads/master.tar.gz\\"); ' + + 'stopifnot(\\"rdmaddins\\" %in% rownames(installed.packages()))' + ) bash_scripts = f""" if [ -x \\"$(command -v R)\\" ]; then R -e '{install_grdm}'; fi """