From 27e4d6adaa62b5d49dbd1e08a2203c97efb398e3 Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Sun, 5 Oct 2025 17:58:04 -0400 Subject: [PATCH 1/3] Use `UV_CONSTRAINT` and `UV_OVERRIDE` for compat with `keras-hub` / `tensorflow-text` --- R/install.R | 68 ++++++++++++++++++++++++++++++++++++-- inst/keras-constraints.txt | 9 +++++ inst/tf-cpu-override.txt | 1 + tools/install.R | 3 ++ 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 inst/keras-constraints.txt create mode 100644 inst/tf-cpu-override.txt create mode 100755 tools/install.R diff --git a/R/install.R b/R/install.R index 62e83a312..350578be2 100644 --- a/R/install.R +++ b/R/install.R @@ -170,6 +170,8 @@ use_backend <- function(backend, gpu = NA) { reticulate::import("os")$environ$update(list(KERAS_BACKEND = backend)) } + set_envvar("UV_CONSTRAINT", pkg_file("keras-constraints.txt"), + action = "append", sep = " ", unique = TRUE) switch( paste0(get_os(), "_", backend), @@ -222,11 +224,11 @@ use_backend <- function(backend, gpu = NA) { gpu <- has_gpu() if (gpu) { + uv_unset_tf_cpu_override() py_require(action = "remove", c("tensorflow", "tensorflow-cpu")) py_require("tensorflow[and-cuda]") } else { - py_require(action = "remove", c("tensorflow", "tensorflow[and-cuda]")) - py_require("tensorflow-cpu") + py_require_tf_cpu() } }, @@ -234,6 +236,7 @@ use_backend <- function(backend, gpu = NA) { py_require(action = "remove", c("tensorflow", "tensorflow[and-cuda]", "jax[cuda12]", "jax[cpu]")) + py_require_tf_cpu() if (is.na(gpu)) gpu <- has_gpu() @@ -248,6 +251,7 @@ use_backend <- function(backend, gpu = NA) { Linux_torch = { py_require(c("tensorflow", "tensorflow[and-cuda]"), action = "remove") + py_require_tf_cpu() if (is.na(gpu)) gpu <- has_gpu() @@ -264,6 +268,7 @@ use_backend <- function(backend, gpu = NA) { }, Linux_numpy = { + py_require_tf_cpu() py_require(c("tensorflow", "tensorflow[and-cuda]"), action = "remove") py_require(c("tensorflow-cpu", "numpy", "jax[cpu]")) }, @@ -301,8 +306,60 @@ use_backend <- function(backend, gpu = NA) { invisible(backend) } +set_envvar <- function( + name, + value, + action = c("replace", "append", "prepend"), + sep = .Platform$path.sep, + unique = FALSE +) { + old <- Sys.getenv(name, NA) + + if (is.null(value) || is.na(value)) { + Sys.unsetenv(name) + return(invisible(old)) + } + + if (!is.na(old)) { + value <- switch( + match.arg(action), + replace = value, + append = paste(old, value, sep = sep), + prepend = paste(value, old, sep = sep) + ) + if (unique) { + value <- unique(unlist(strsplit(value, sep, fixed = TRUE))) + value <- paste0(value, collapse = sep) + } + } + + do.call(Sys.setenv, setNames(list(value), name)) + invisible(old) +} +py_require_tf_cpu <- function() { + py_require(action = "remove", c( + "tensorflow", "tensorflow[and-cuda]", "tensorflow-cpu", + "tensorflow-metal", "tensorflow-macos" + )) + py_require(if (is_linux()) "tensorflow-cpu" else "tensorflow") + set_envvar("UV_OVERRIDE", pkg_file("tf-cpu-override.txt"), + action = "append", sep = " ", unique = TRUE) +} +uv_unset_tf_cpu_override <- function() { + override <- Sys.getenv("UV_OVERRIDE", NA) + if (is.na(override)) return() + cpu_override <- pkg_file("tf-cpu-override.txt") + if (override == cpu_override) { + Sys.unsetenv(override) + } else { + new <- gsub(cpu_override, "", override, fixed = TRUE) + new <- gsub(" +", " ", new) + Sys.setenv("UV_OVERRIDE" = new) + } + invisible(override) +} get_os <- function() { if (is_windows()) "Windows" else if (is_mac_arm64()) "macOS" else "Linux" @@ -321,6 +378,13 @@ is_keras_loaded <- function() { !exists("module", envir = keras) } +pkg_file <- function(..., package = "keras3") { + path <- system.file(..., package = "keras3", mustWork = TRUE) + if(is_windows()) + path <- utils::shortPathName(path) + path +} + has_gpu <- function() { diff --git a/inst/keras-constraints.txt b/inst/keras-constraints.txt new file mode 100644 index 000000000..3ca707edb --- /dev/null +++ b/inst/keras-constraints.txt @@ -0,0 +1,9 @@ + +# unconstrained keras-hub in a larger requirements list might resolve to v0.19.0, +# which is over a year old and generally what people want, and arguably a bug with `uv`. +# This is a workaround to nudge uv to resolve the latest keras-hub. +keras-hub>0.19.0 + + +# tensorflow-text 2.19 fails to load with tensorflow-cpu>=2.19 +tensorflow-cpu==2.18.* diff --git a/inst/tf-cpu-override.txt b/inst/tf-cpu-override.txt new file mode 100644 index 000000000..189aaddab --- /dev/null +++ b/inst/tf-cpu-override.txt @@ -0,0 +1 @@ +tensorflow; sys_platform == "never" diff --git a/tools/install.R b/tools/install.R new file mode 100755 index 000000000..e1bf00e39 --- /dev/null +++ b/tools/install.R @@ -0,0 +1,3 @@ +#!/usr/bin/Rscript + +remotes::install_local(force = TRUE) From 6ee05934d3751e1ef58479a282b86c9630c317ab Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Sun, 5 Oct 2025 18:05:17 -0400 Subject: [PATCH 2/3] better names --- R/install.R | 14 +++++++------- inst/keras-constraints.txt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/R/install.R b/R/install.R index 350578be2..5a22358fe 100644 --- a/R/install.R +++ b/R/install.R @@ -224,11 +224,11 @@ use_backend <- function(backend, gpu = NA) { gpu <- has_gpu() if (gpu) { - uv_unset_tf_cpu_override() + uv_unset_override_tf_cpu() py_require(action = "remove", c("tensorflow", "tensorflow-cpu")) py_require("tensorflow[and-cuda]") } else { - py_require_tf_cpu() + uv_set_override_tf_cpu() } }, @@ -236,7 +236,7 @@ use_backend <- function(backend, gpu = NA) { py_require(action = "remove", c("tensorflow", "tensorflow[and-cuda]", "jax[cuda12]", "jax[cpu]")) - py_require_tf_cpu() + uv_set_override_tf_cpu() if (is.na(gpu)) gpu <- has_gpu() @@ -251,7 +251,7 @@ use_backend <- function(backend, gpu = NA) { Linux_torch = { py_require(c("tensorflow", "tensorflow[and-cuda]"), action = "remove") - py_require_tf_cpu() + uv_set_override_tf_cpu() if (is.na(gpu)) gpu <- has_gpu() @@ -268,7 +268,7 @@ use_backend <- function(backend, gpu = NA) { }, Linux_numpy = { - py_require_tf_cpu() + uv_set_override_tf_cpu() py_require(c("tensorflow", "tensorflow[and-cuda]"), action = "remove") py_require(c("tensorflow-cpu", "numpy", "jax[cpu]")) }, @@ -337,7 +337,7 @@ set_envvar <- function( invisible(old) } -py_require_tf_cpu <- function() { +uv_set_override_tf_cpu <- function() { py_require(action = "remove", c( "tensorflow", "tensorflow[and-cuda]", "tensorflow-cpu", "tensorflow-metal", "tensorflow-macos" @@ -347,7 +347,7 @@ py_require_tf_cpu <- function() { action = "append", sep = " ", unique = TRUE) } -uv_unset_tf_cpu_override <- function() { +uv_unset_override_tf_cpu <- function() { override <- Sys.getenv("UV_OVERRIDE", NA) if (is.na(override)) return() cpu_override <- pkg_file("tf-cpu-override.txt") diff --git a/inst/keras-constraints.txt b/inst/keras-constraints.txt index 3ca707edb..a2780d921 100644 --- a/inst/keras-constraints.txt +++ b/inst/keras-constraints.txt @@ -5,5 +5,5 @@ keras-hub>0.19.0 -# tensorflow-text 2.19 fails to load with tensorflow-cpu>=2.19 +# tensorflow-text 2.19.* fails to load with tensorflow-cpu>=2.19.0 tensorflow-cpu==2.18.* From da7f902dbc253f537edfe459f65b85556374f2e7 Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Mon, 6 Oct 2025 09:37:57 -0400 Subject: [PATCH 3/3] fix CI --- R/install.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/install.R b/R/install.R index 5a22358fe..0c7e33f48 100644 --- a/R/install.R +++ b/R/install.R @@ -333,7 +333,9 @@ set_envvar <- function( } } - do.call(Sys.setenv, setNames(list(value), name)) + value <- list(value) + names(value) <- name + do.call(Sys.setenv, value) invisible(old) }