From 47087d9988dc731b3ea0cd2c8671d9ed6a4fe409 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 8 Jun 2026 14:15:28 -0500 Subject: [PATCH 1/3] feat(r.j2): add manylinux_2_34 R tarball install macros Adds install_manylinux() and run_install_manylinux() as alternatives to the RPM/deb path. Downloads pre-built R tarballs from cdn.posit.co/r/ and extracts them directly to /opt/R/{version}/, bypassing the package manager's dependency resolution. Requires glibc >= 2.34 (RHEL 10, Ubuntu 22.04+, Debian 12+). Arch is detected at runtime so the same Containerfile builds on amd64 and arm64. --- .../config/templating/macros/r.j2 | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/posit-bakery/posit_bakery/config/templating/macros/r.j2 b/posit-bakery/posit_bakery/config/templating/macros/r.j2 index d38f411f0..e1ba7d6d3 100644 --- a/posit-bakery/posit_bakery/config/templating/macros/r.j2 +++ b/posit-bakery/posit_bakery/config/templating/macros/r.j2 @@ -26,6 +26,36 @@ RUN_UNATTENDED=1 R_VERSION={{ version | trim }} bash -c "$(curl -fsSL https://rs find . -type f -name '[rR]-{{ version | trim }}.*\.(deb|rpm)' -delete {%- endmacro %} +{# Downloads and extracts a manylinux_2_34 R tarball from cdn.posit.co/r directly to +/opt/R/{version}/. Requires glibc >= 2.34 (RHEL 10, Ubuntu 22.04+, Debian 12+). +Use in place of run_install when the platform cannot satisfy the RPM/deb dependencies. + +:param version: The R version to install, e.g. "4.4.3" +#} +{% macro install_manylinux(version) -%} +{%- set v = version | trim -%} +ARCH=$(uname -m) && \ +ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \ +curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-{{ v }}-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \ + | tar -xz -C /opt/R/ +{%- endmacro %} + +{# Installs one or more versions of R from manylinux_2_34 tarballs as separate RUN statements + +:param versions: A list of R versions or a comma separated string of R versions to install +#} +{% macro run_install_manylinux(versions) -%} +{%- if versions is string -%} +{%- set versions = versions | split(",") | map('trim') -%} +{%- endif -%} +RUN mkdir -p /opt/R +{% for version in versions -%} +RUN {{ install_manylinux(version) | indent(4) }} +{%- if not loop.last %} +{% endif %} +{%- endfor %} +{%- endmacro %} + {# Installs one or more versions of R as separate RUN statements :param versions: A list of R versions or a comma separated string of R versions to install From 5e525e7bc933ae6b0cec31f8228fe621b2fd0acb Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 8 Jun 2026 15:11:14 -0500 Subject: [PATCH 2/3] feat(r.j2): add glibc_version param to manylinux macros Allows callers to select which manylinux variant to download from cdn.posit.co/r/. Defaults to "2_34", which covers RHEL 9+, Ubuntu 22.04+, and Debian 12+. Passing a different value (e.g. "2_28") will select the corresponding tarball variant. --- .../config/templating/macros/r.j2 | 20 +++-- .../test/config/templating/test_macros.py | 75 +++++++++++++++++++ 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/posit-bakery/posit_bakery/config/templating/macros/r.j2 b/posit-bakery/posit_bakery/config/templating/macros/r.j2 index e1ba7d6d3..02dd7aa77 100644 --- a/posit-bakery/posit_bakery/config/templating/macros/r.j2 +++ b/posit-bakery/posit_bakery/config/templating/macros/r.j2 @@ -26,31 +26,35 @@ RUN_UNATTENDED=1 R_VERSION={{ version | trim }} bash -c "$(curl -fsSL https://rs find . -type f -name '[rR]-{{ version | trim }}.*\.(deb|rpm)' -delete {%- endmacro %} -{# Downloads and extracts a manylinux_2_34 R tarball from cdn.posit.co/r directly to -/opt/R/{version}/. Requires glibc >= 2.34 (RHEL 10, Ubuntu 22.04+, Debian 12+). -Use in place of run_install when the platform cannot satisfy the RPM/deb dependencies. +{# Downloads and extracts a manylinux R tarball from cdn.posit.co/r directly to +/opt/R/{version}/. Use in place of run_install when the platform cannot satisfy +the RPM/deb dependencies. :param version: The R version to install, e.g. "4.4.3" +:param glibc_version: The manylinux glibc version string, e.g. "2_34" (default). + Requires the host glibc to be >= the chosen version. #} -{% macro install_manylinux(version) -%} +{% macro install_manylinux(version, glibc_version="2_34") -%} {%- set v = version | trim -%} ARCH=$(uname -m) && \ ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \ -curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-{{ v }}-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \ +curl -fsSL "https://cdn.posit.co/r/manylinux_{{ glibc_version }}/R-{{ v }}-manylinux_{{ glibc_version }}${ARCH_SUFFIX}.tar.gz" \ | tar -xz -C /opt/R/ {%- endmacro %} -{# Installs one or more versions of R from manylinux_2_34 tarballs as separate RUN statements +{# Installs one or more versions of R from manylinux tarballs as separate RUN statements :param versions: A list of R versions or a comma separated string of R versions to install +:param glibc_version: The manylinux glibc version string, e.g. "2_34" (default). + Passed through to install_manylinux. #} -{% macro run_install_manylinux(versions) -%} +{% macro run_install_manylinux(versions, glibc_version="2_34") -%} {%- if versions is string -%} {%- set versions = versions | split(",") | map('trim') -%} {%- endif -%} RUN mkdir -p /opt/R {% for version in versions -%} -RUN {{ install_manylinux(version) | indent(4) }} +RUN {{ install_manylinux(version, glibc_version) | indent(4) }} {%- if not loop.last %} {% endif %} {%- endfor %} diff --git a/posit-bakery/test/config/templating/test_macros.py b/posit-bakery/test/config/templating/test_macros.py index ce71d07c5..b94f6e367 100644 --- a/posit-bakery/test/config/templating/test_macros.py +++ b/posit-bakery/test/config/templating/test_macros.py @@ -2385,6 +2385,81 @@ def test_run_install(self, environment_with_macros, input, expected): rendered = environment_with_macros.from_string(template).render() assert rendered == expected + def test_install_manylinux_default(self, environment_with_macros): + template = '{%- import "r.j2" as r -%}\n{{ r.install_manylinux("4.6.0") }}' + expected = textwrap.dedent( + """\ + ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.6.0-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/""" + ) + rendered = environment_with_macros.from_string(template).render() + assert rendered == expected + + def test_install_manylinux_explicit_version(self, environment_with_macros): + template = '{%- import "r.j2" as r -%}\n{{ r.install_manylinux("4.6.0", glibc_version="2_28") }}' + expected = textwrap.dedent( + """\ + ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_28/R-4.6.0-manylinux_2_28${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/""" + ) + rendered = environment_with_macros.from_string(template).render() + assert rendered == expected + + @pytest.mark.parametrize( + "input,expected", + [ + pytest.param( + (["4.6.0"],), + textwrap.dedent( + """\ + RUN mkdir -p /opt/R + RUN ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.6.0-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/""" + ), + id="single-version-default-glibc", + ), + pytest.param( + (["4.6.0", "4.4.3"],), + textwrap.dedent( + """\ + RUN mkdir -p /opt/R + RUN ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.6.0-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/ + RUN ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.4.3-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/""" + ), + id="multiple-versions-default-glibc", + ), + pytest.param( + (["4.6.0"], "2_28"), + textwrap.dedent( + """\ + RUN mkdir -p /opt/R + RUN ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_28/R-4.6.0-manylinux_2_28${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/""" + ), + id="explicit-glibc-version", + ), + ], + ) + def test_run_install_manylinux(self, environment_with_macros, input, expected): + parts = [f'"{i}"' if isinstance(i, str) else str(i) for i in input] + template = '{%- import "r.j2" as r -%}\n{{ r.run_install_manylinux(' + ", ".join(parts) + ") }}" + rendered = environment_with_macros.from_string(template).render() + assert rendered == expected + @pytest.mark.parametrize( "_os,expected", [ From 15b850258d56d82dbf39264d99bb83d848622b16 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 8 Jun 2026 15:14:14 -0500 Subject: [PATCH 3/3] test(r.j2): improve manylinux macro test coverage Add string-versions case for run_install_manylinux and split the explicit-glibc-version case into a standalone test. Removes the isinstance conditional from the parametrize helper and brings coverage in line with test_run_install. --- .../test/config/templating/test_macros.py | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/posit-bakery/test/config/templating/test_macros.py b/posit-bakery/test/config/templating/test_macros.py index b94f6e367..641f2c90b 100644 --- a/posit-bakery/test/config/templating/test_macros.py +++ b/posit-bakery/test/config/templating/test_macros.py @@ -2413,7 +2413,7 @@ def test_install_manylinux_explicit_version(self, environment_with_macros): "input,expected", [ pytest.param( - (["4.6.0"],), + ["4.6.0"], textwrap.dedent( """\ RUN mkdir -p /opt/R @@ -2422,10 +2422,10 @@ def test_install_manylinux_explicit_version(self, environment_with_macros): curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.6.0-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ | tar -xz -C /opt/R/""" ), - id="single-version-default-glibc", + id="single-version", ), pytest.param( - (["4.6.0", "4.4.3"],), + ["4.6.0", "4.4.3"], textwrap.dedent( """\ RUN mkdir -p /opt/R @@ -2438,25 +2438,41 @@ def test_install_manylinux_explicit_version(self, environment_with_macros): curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.4.3-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ | tar -xz -C /opt/R/""" ), - id="multiple-versions-default-glibc", + id="multiple-versions", ), pytest.param( - (["4.6.0"], "2_28"), + "'4.6.0,4.4.3'", textwrap.dedent( """\ RUN mkdir -p /opt/R RUN ARCH=$(uname -m) && \\ ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ - curl -fsSL "https://cdn.posit.co/r/manylinux_2_28/R-4.6.0-manylinux_2_28${ARCH_SUFFIX}.tar.gz" \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.6.0-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/ + RUN ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_34/R-4.4.3-manylinux_2_34${ARCH_SUFFIX}.tar.gz" \\ | tar -xz -C /opt/R/""" ), - id="explicit-glibc-version", + id="string-versions", ), ], ) def test_run_install_manylinux(self, environment_with_macros, input, expected): - parts = [f'"{i}"' if isinstance(i, str) else str(i) for i in input] - template = '{%- import "r.j2" as r -%}\n{{ r.run_install_manylinux(' + ", ".join(parts) + ") }}" + template = '{%- import "r.j2" as r -%}\n{{ r.run_install_manylinux(' + str(input) + ") }}" + rendered = environment_with_macros.from_string(template).render() + assert rendered == expected + + def test_run_install_manylinux_explicit_glibc_version(self, environment_with_macros): + template = '{%- import "r.j2" as r -%}\n{{ r.run_install_manylinux(["4.6.0"], "2_28") }}' + expected = textwrap.dedent( + """\ + RUN mkdir -p /opt/R + RUN ARCH=$(uname -m) && \\ + ARCH_SUFFIX=$([ "$ARCH" = "aarch64" ] && echo "-arm64" || echo "") && \\ + curl -fsSL "https://cdn.posit.co/r/manylinux_2_28/R-4.6.0-manylinux_2_28${ARCH_SUFFIX}.tar.gz" \\ + | tar -xz -C /opt/R/""" + ) rendered = environment_with_macros.from_string(template).render() assert rendered == expected