-
Notifications
You must be signed in to change notification settings - Fork 2
feat: experimental uvr backend (shiny2docker_uvr) #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
cb64451
e11ea24
50e6cfe
9df9837
8570e52
0a7b2e5
6b295bc
8e31564
d821744
a83445e
93cac67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| #' shiny2docker_uvr | ||
| #' | ||
| #' Generate a Dockerfile for a Shiny application using | ||
| #' [uvr](https://github.com/nbafrank/uvr) instead of `renv`. The R version is | ||
| #' installed inside the container by `uvr` (no `rocker/r-ver` base image | ||
| #' required) and packages are restored from `uvr.lock` via `uvr sync --frozen`. | ||
| #' | ||
| #' Lifecycle: experimental. Requires the project to already be uvr-managed | ||
| #' (`uvr.toml`, `uvr.lock`, and `.r-version` present at the project root). | ||
| #' | ||
| #' @param path Character. Path to the folder containing the Shiny application. | ||
| #' @param output Character. Path to the generated Dockerfile. | ||
| #' @param uvr_toml Path to the `uvr.toml` manifest. Defaults to `<path>/uvr.toml`. | ||
| #' @param uvr_lock Path to the `uvr.lock` lockfile. Defaults to `<path>/uvr.lock`. | ||
| #' @param r_version_file Path to the R version pin. Defaults to `<path>/.r-version`. | ||
| #' @param base_image Character. Docker base image. Defaults to `"debian:stable-slim"`. | ||
| #' @param uvr_version Character. `"latest"` or a semver tag like `"v0.3.1"`. | ||
| #' Pinning a tag is recommended for reproducible builds. | ||
| #' @param port Integer. Shiny port to expose. Default `3838`. | ||
| #' @param host Character. Shiny host. Default `"0.0.0.0"`. | ||
| #' @param extra_sysreqs Character vector of extra debian packages to install | ||
| #' before `uvr sync` (escape hatch for system dependencies that uvr does not | ||
| #' detect on its own). | ||
| #' @param write Logical. Whether to write the Dockerfile to `output`. Default `TRUE`. | ||
| #' | ||
| #' @return Invisibly, an R6 `dockerfiler::Dockerfile` object that can be further | ||
| #' customised before writing. | ||
| #' | ||
| #' @export | ||
| #' | ||
| #' @importFrom dockerfiler Dockerfile | ||
| shiny2docker_uvr <- function(path = ".", | ||
| output = file.path(path, "Dockerfile"), | ||
| uvr_toml = file.path(path, "uvr.toml"), | ||
| uvr_lock = file.path(path, "uvr.lock"), | ||
| r_version_file = file.path(path, ".r-version"), | ||
| base_image = "debian:stable-slim", | ||
| uvr_version = "latest", | ||
| port = 3838, | ||
| host = "0.0.0.0", | ||
| extra_sysreqs = NULL, | ||
| write = TRUE) { | ||
|
|
||
| uvr_assert_state( | ||
| uvr_toml = uvr_toml, | ||
| uvr_lock = uvr_lock, | ||
| r_version_file = r_version_file | ||
| ) | ||
|
|
||
| if (!file.exists(file.path(dirname(output), ".dockerignore"))) { | ||
| create_dockerignore_uvr(path = file.path(dirname(output), ".dockerignore")) | ||
| } | ||
|
|
||
| dock <- uvr_build_dockerfile( | ||
| base_image = base_image, | ||
| uvr_version = uvr_version, | ||
| port = port, | ||
| host = host, | ||
| extra_sysreqs = extra_sysreqs | ||
| ) | ||
|
|
||
| if (isTRUE(write)) { | ||
| dock$write(output) | ||
| } | ||
|
|
||
| invisible(dock) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,198 @@ | ||||||||
| #' Write a uvr-aware .dockerignore | ||||||||
| #' | ||||||||
| #' Adds the entries that are specific to a uvr-managed project (`.uvr/`, | ||||||||
| #' `.Rprofile` written by `uvr init`) on top of the defaults used by the | ||||||||
| #' `renv` backend. | ||||||||
| #' | ||||||||
| #' @param path Path to the `.dockerignore` to write. | ||||||||
| #' @noRd | ||||||||
| create_dockerignore_uvr <- function(path = ".dockerignore") { | ||||||||
| entries <- c( | ||||||||
| ".Rhistory", | ||||||||
| ".git", | ||||||||
| ".gitignore", | ||||||||
| ".Rproj.user", | ||||||||
| "manifest.json", | ||||||||
| "rsconnect/", | ||||||||
| ".uvr/", | ||||||||
| "renv/", | ||||||||
| "renv.lock" | ||||||||
| ) | ||||||||
|
VincentGuyader marked this conversation as resolved.
|
||||||||
| writeLines(entries, con = path) | ||||||||
| } | ||||||||
|
|
||||||||
| #' Assert that the project is uvr-ready | ||||||||
| #' | ||||||||
| #' Checks that `uvr.toml`, `uvr.lock` and a R version pin file all exist, and | ||||||||
| #' that `uvr.lock` is at least as recent as `uvr.toml`. Stops with an actionable | ||||||||
| #' message otherwise. | ||||||||
| #' | ||||||||
| #' @param uvr_toml Path to `uvr.toml`. | ||||||||
| #' @param uvr_lock Path to `uvr.lock`. | ||||||||
| #' @param r_version_file Path to `.r-version`. | ||||||||
| #' | ||||||||
| #' @return Invisibly `TRUE` on success. | ||||||||
| #' @noRd | ||||||||
| uvr_assert_state <- function(uvr_toml, uvr_lock, r_version_file) { | ||||||||
|
|
||||||||
| if (!file.exists(uvr_toml)) { | ||||||||
| stop( | ||||||||
| "uvr.toml not found at '", uvr_toml, "'.\n", | ||||||||
| "Run `uvr init` (or `uvr import` from an existing renv.lock), then `uvr lock`.", | ||||||||
| call. = FALSE | ||||||||
| ) | ||||||||
| } | ||||||||
| if (!file.exists(uvr_lock)) { | ||||||||
| stop( | ||||||||
| "uvr.lock not found at '", uvr_lock, "'.\n", | ||||||||
| "Run `uvr lock` to generate it.", | ||||||||
| call. = FALSE | ||||||||
| ) | ||||||||
| } | ||||||||
| if (file.info(uvr_lock)$mtime < file.info(uvr_toml)$mtime) { | ||||||||
| stop( | ||||||||
| "uvr.lock is older than uvr.toml — manifest changed since last lock.\n", | ||||||||
| "Run `uvr lock` to refresh it.", | ||||||||
| call. = FALSE | ||||||||
| ) | ||||||||
| } | ||||||||
| if (!file.exists(r_version_file)) { | ||||||||
| stop( | ||||||||
| ".r-version not found at '", r_version_file, "'.\n", | ||||||||
| "Run `uvr r pin <version>` to pin the R version for the project.", | ||||||||
| call. = FALSE | ||||||||
| ) | ||||||||
| } | ||||||||
|
|
||||||||
| invisible(TRUE) | ||||||||
| } | ||||||||
|
VincentGuyader marked this conversation as resolved.
VincentGuyader marked this conversation as resolved.
|
||||||||
|
|
||||||||
| #' Build the uvr release download URL | ||||||||
| #' | ||||||||
| #' @param uvr_version Either `"latest"` or a tag like `"v0.3.1"` / `"0.3.1"`. | ||||||||
| #' @return A list with `url` and `arg_value`. `arg_value` is `NA` when | ||||||||
| #' `uvr_version = "latest"` (no `ARG UVR_VERSION` injected). | ||||||||
| #' @noRd | ||||||||
| uvr_release_url <- function(uvr_version = "latest") { | ||||||||
| asset <- "uvr-x86_64-unknown-linux-gnu.tar.gz" | ||||||||
| base <- "https://github.com/nbafrank/uvr/releases" | ||||||||
|
|
||||||||
| if (identical(uvr_version, "latest")) { | ||||||||
| return(list( | ||||||||
| url = sprintf("%s/latest/download/%s", base, asset), | ||||||||
| arg_value = NA_character_ | ||||||||
| )) | ||||||||
| } | ||||||||
|
|
||||||||
| tag <- if (startsWith(uvr_version, "v")) uvr_version else paste0("v", uvr_version) | ||||||||
| if (!grepl("^v[0-9]+\\.[0-9]+\\.[0-9]+", tag)) { | ||||||||
| stop( | ||||||||
| "uvr_version must be 'latest' or a semver tag like 'v0.3.1' / '0.3.1'. ", | ||||||||
| "Got: '", uvr_version, "'", | ||||||||
| call. = FALSE | ||||||||
| ) | ||||||||
| } | ||||||||
|
VincentGuyader marked this conversation as resolved.
|
||||||||
| list( | ||||||||
| url = sprintf("%s/download/%s/%s", base, tag, asset), | ||||||||
| arg_value = tag | ||||||||
| ) | ||||||||
| } | ||||||||
|
|
||||||||
| #' Build the Dockerfile object for the uvr backend | ||||||||
| #' | ||||||||
| #' @param base_image Base OS image (e.g. `"debian:stable-slim"`). | ||||||||
| #' @param uvr_version `"latest"` or a tag (`"v0.3.1"`). | ||||||||
| #' @param port Shiny port. | ||||||||
| #' @param host Shiny host. | ||||||||
| #' @param extra_sysreqs Optional character vector of extra debian packages to | ||||||||
| #' `apt-get install` before `uvr sync`. | ||||||||
| #' | ||||||||
| #' @return An R6 `dockerfiler::Dockerfile` object. | ||||||||
| #' @noRd | ||||||||
| uvr_build_dockerfile <- function(base_image = "debian:stable-slim", | ||||||||
| uvr_version = "latest", | ||||||||
| port = 3838, | ||||||||
| host = "0.0.0.0", | ||||||||
| extra_sysreqs = NULL) { | ||||||||
|
|
||||||||
| release <- uvr_release_url(uvr_version) | ||||||||
|
|
||||||||
| dock <- dockerfiler::Dockerfile$new(FROM = base_image) | ||||||||
|
|
||||||||
| dock$ENV("DEBIAN_FRONTEND", "noninteractive") | ||||||||
| dock$ENV("LANG", "en_US.UTF-8") | ||||||||
| dock$ENV("LC_ALL", "en_US.UTF-8") | ||||||||
|
|
||||||||
| dock$RUN(paste( | ||||||||
| "apt-get update && apt-get install -y --no-install-recommends", | ||||||||
| # base tooling | ||||||||
| "ca-certificates curl locales tzdata", | ||||||||
| # binutils provides `ar`, used by `uvr r install` to extract the Posit .deb | ||||||||
| "binutils", | ||||||||
| # runtime + build deps required by Posit's R .deb | ||||||||
| "g++ gcc gfortran make zip unzip ucf", | ||||||||
| "libbz2-dev libc6 libcairo2 libcurl4-openssl-dev libdeflate-dev", | ||||||||
| "libglib2.0-0 libgomp1 libicu-dev liblzma-dev libopenblas-dev", | ||||||||
| "libpango-1.0-0 libpangocairo-1.0-0 libpaper-utils libpcre2-dev", | ||||||||
| "libpng16-16 libreadline8 libtcl8.6 libtiff6 libtirpc-dev libtk8.6", | ||||||||
| "libx11-6 libxt6 zlib1g-dev", | ||||||||
| # extra deps commonly needed when uvr falls back to source compilation | ||||||||
| # (P3M binaries are not always available on debian targets as of uvr 0.2.15) | ||||||||
| "libuv1-dev libxml2-dev libssl-dev libsodium-dev", | ||||||||
| "&& sed -i '/en_US.UTF-8/s/^# //' /etc/locale.gen && locale-gen", | ||||||||
| "&& rm -rf /var/lib/apt/lists/*" | ||||||||
| )) | ||||||||
|
|
||||||||
| if (!is.na(release$arg_value)) { | ||||||||
| dock$ARG(paste0("UVR_VERSION=", release$arg_value)) | ||||||||
| } | ||||||||
|
|
||||||||
|
Comment on lines
+483
to
+486
|
||||||||
| if (!is.na(release$arg_value)) { | |
| dock$ARG(paste0("UVR_VERSION=", release$arg_value)) | |
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /.uvr/library/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 4.4.2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| library(shiny) | ||
| ui <- fluidPage( | ||
| titlePanel("uvr POC"), | ||
| textOutput("msg") | ||
| ) | ||
| server <- function(input, output, session) { | ||
| output$msg <- renderText("Hello from a uvr-built shiny container.") | ||
| } | ||
| shinyApp(ui, server) |
Uh oh!
There was an error while loading. Please reload this page.