Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/java-live-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ jobs:
extra-packages: any::rcmdcheck
needs: check

- name: Log Java diagnostics
shell: bash
run: |
java -version || true
Rscript -e 'cat(Sys.getenv("JAVA_HOME"), "\n")'

- uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true
Expand Down
142 changes: 89 additions & 53 deletions R/internal_utilities.R
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,70 @@ read_json_url <- function(url, max_simplify_lvl = "data_frame") {
RcppSimdJson::fparse(content, max_simplify_lvl = max_simplify_lvl)
}

#' Build environment variables for a Java subprocess
#'
#' @param java_home Path to Java home directory.
#' @param rjava Logical. Whether the subprocess will initialize rJava.
#'
#' @return Named character vector of environment variables.
#' @keywords internal
#' @noRd
java_subprocess_env <- function(java_home, rjava = FALSE) {
checkmate::assert_string(java_home)
checkmate::assert_logical(rjava, len = 1)

env_vars <- Sys.getenv()
env_get <- function(name) {
if (name %in% names(env_vars)) {
env_vars[[name]]
} else {
NA_character_
}
}
java_bin <- file.path(java_home, "bin")
old_path <- env_get("PATH")

env_vars["JAVA_HOME"] <- java_home
env_vars["PATH"] <- paste(java_bin, old_path, sep = .Platform$path.sep)

if (!isTRUE(rjava)) {
return(env_vars)
}

sysname <- Sys.info()[["sysname"]]
libjvm_path <- get_libjvm_path(java_home)
if (is.null(libjvm_path)) {
return(env_vars)
}

jvm_lib_dir <- dirname(libjvm_path)

if (identical(sysname, "Linux")) {
old_ld <- env_get("LD_LIBRARY_PATH")
if (is.na(old_ld)) {
old_ld <- ""
}
env_vars["JAVA_LD_LIBRARY_PATH"] <- jvm_lib_dir
env_vars["LD_LIBRARY_PATH"] <- if (nzchar(old_ld)) {
paste(jvm_lib_dir, old_ld, sep = .Platform$path.sep)
} else {
jvm_lib_dir
}
} else if (identical(sysname, "Darwin")) {
old_dyld <- env_get("DYLD_LIBRARY_PATH")
if (is.na(old_dyld)) {
old_dyld <- ""
}
env_vars["DYLD_LIBRARY_PATH"] <- if (nzchar(old_dyld)) {
paste(jvm_lib_dir, old_dyld, sep = .Platform$path.sep)
} else {
jvm_lib_dir
}
}

env_vars
}

#' Read lines from a URL or file
#'
#' Helper function to read lines, mainly for testability.
Expand Down Expand Up @@ -148,44 +212,9 @@ urls_test_all <- function() {
java_version_check_rscript <- function(java_home) {
result <- tryCatch(
{
Sys.setenv(JAVA_HOME = java_home)

old_path <- Sys.getenv("PATH")
new_path <- file.path(java_home, "bin")
Sys.setenv(PATH = paste(new_path, old_path, sep = .Platform$path.sep))

# On Linux, find and dynamically load libjvm.so
if (Sys.info()["sysname"] == "Linux") {
libjvm_path <- get_libjvm_path(java_home)

if (!is.null(libjvm_path) && file.exists(libjvm_path)) {
tryCatch(
dyn.load(libjvm_path),
error = function(e) {
# Use base message to avoid dependency issues in the isolated script
message(sprintf(
"Found libjvm.so at '%s' but failed to load it: %s",
libjvm_path,
e$message
))
}
)
} else {
message(sprintf(
"Could not find libjvm.so within the provided JAVA_HOME: %s",
java_home
))
}
}

suppressWarnings(rJava::.jinit())
suppressWarnings(
java_version <- rJava::.jcall(
"java.lang.System",
"S",
"getProperty",
"java.version"
)
java_version <- suppressWarnings(
rJava::.jcall("java.lang.System", "S", "getProperty", "java.version")
)

message <- cli::format_message(
Expand All @@ -202,6 +231,28 @@ java_version_check_rscript <- function(java_home) {
return(result)
}

#' Parse major Java version from a java.version string
#'
#' @param java_ver_str Character string containing Java version.
#'
#' @return Character scalar major version or NULL.
#' @keywords internal
#' @noRd
parse_java_major_version <- function(java_ver_str) {
if (is.null(java_ver_str) || !nzchar(java_ver_str)) {
return(NULL)
}

matches <- regexec("^(1\\.)?([0-9]+)", java_ver_str)
parts <- regmatches(java_ver_str, matches)[[1]]

if (length(parts) < 3) {
return(NULL)
}

parts[3]
}

#' Find path to libjvm dynamic library
#'
#' @description
Expand Down Expand Up @@ -394,22 +445,7 @@ java_check_current_rjava_version <- function() {
return(NULL)
}

# Parse version: "1.8.0_..." -> "8", "17.0.1" -> "17"
matches <- regexec(
"^(1\\.)?([0-9]+)",
java_ver_str
)
parts <- regmatches(java_ver_str, matches)[[1]]

if (length(parts) < 3) {
return(NULL)
}

major <- parts[3]
# Handle 1.8 -> 8 case (parts[2] is "1." and parts[3] is "8")
# Handle 17 -> 17 case (parts[2] is "" and parts[3] is "17")

return(major)
parse_java_major_version(java_ver_str)
}

#' Find the actual extracted directory, ignoring hidden/metadata files
Expand Down
99 changes: 65 additions & 34 deletions R/java_env.R
Original file line number Diff line number Diff line change
Expand Up @@ -305,32 +305,67 @@ java_check_version_rjava <- function(

# Original implementation (not memoised) - used when .use_cache = FALSE
._java_version_check_rjava_impl_original <- function(java_home = NULL) {
# Get the code of the unexported functions to use in a script
# On Linux, the script depends on get_libjvm_path() to avoid crashes in .jinit()
if (is.null(java_home) || !nzchar(java_home)) {
return(FALSE)
}

if (requireNamespace("callr", quietly = TRUE)) {
data <- tryCatch(
{
callr::r(
func = function() {
suppressWarnings(rJava::.jinit())
java_version <- suppressWarnings(
rJava::.jcall(
"java.lang.System",
"S",
"getProperty",
"java.version"
)
)

list(
java_version = java_version,
output = cli::format_message(
paste0(
"rJava and other rJava/Java-based packages will use ",
"Java version: {.val {java_version}}"
)
)
)
},
libpath = .libPaths(),
env = java_subprocess_env(java_home, rjava = TRUE),
show = FALSE
)
},
error = function(e) FALSE
)

if (!isFALSE(data)) {
major_java_ver <- parse_java_major_version(data$java_version)
if (!is.null(major_java_ver)) {
return(list(
major_version = major_java_ver,
output = data$output
))
}
}
}
java_version_check_fn <- getFromNamespace(
"java_version_check_rscript",
"rJavaEnv"
)
get_libjvm_path_fn <- getFromNamespace("get_libjvm_path", "rJavaEnv")

# Helper to deparse and collapse multi-line expressions
deparse_collapse <- function(x) {
paste(deparse(x, width.cutoff = 500), collapse = "\n")
}

java_version_check_body <- deparse_collapse(body(java_version_check_fn))
get_libjvm_path_body <- deparse_collapse(body(get_libjvm_path_fn))
libs_val <- deparse_collapse(as.character(.libPaths()))

# Create a wrapper script that includes the function definitions and calls them
# Capture current libPaths to ensure subprocess can find rJava in renv/packrat environments
wrapper_script <- paste0(
".libPaths(",
libs_val,
")\n\n",
"get_libjvm_path <- function(java_home) ",
get_libjvm_path_body,
"\n\n",
"java_version_check <- function(java_home) ",
java_version_check_body,
"\n\n",
Expand All @@ -341,46 +376,42 @@ java_check_version_rjava <- function(
"}"
)

# Write the wrapper script to a temporary file
script_file <- tempfile(fileext = ".R")
writeLines(wrapper_script, script_file)

# Run the script in a separate R session and capture the output
rscript_path <- file.path(R.home("bin"), "Rscript")
output <- suppressWarnings(system2(
rscript_path,
args = c(script_file, java_home),
stdout = TRUE,
stderr = TRUE,
timeout = 5
))
output <- tryCatch(
suppressWarnings(system2(
file.path(R.home("bin"), "Rscript"),
args = c(script_file, java_home),
stdout = TRUE,
stderr = TRUE,
timeout = 5,
env = java_subprocess_env(java_home, rjava = TRUE)
)),
error = function(e) character(0)
)

# Delete the temporary script file
unlink(script_file)

# Process the output (no printing here)
if (length(output) == 0 || any(grepl("error", tolower(output)))) {
return(FALSE)
}

output <- paste(output, collapse = "\n")
cleaned_output <- cli::ansi_strip(output)
major_java_ver <- sub('.*version: \\"([0-9]+).*', '\\1', cleaned_output)
version_matches <- regexec('version: \\"([^\\"]+)\\"', cleaned_output)
version_parts <- regmatches(cleaned_output, version_matches)[[1]]
java_version <- if (length(version_parts) > 1) version_parts[2] else NULL
major_java_ver <- parse_java_major_version(java_version)

if (!nzchar(major_java_ver) || !grepl("^[0-9]+$", major_java_ver)) {
if (is.null(major_java_ver)) {
return(FALSE)
}

# Fix 1 to 8, as Java 8 prints "1.8"
if (major_java_ver == "1") {
major_java_ver <- "8"
}

# Return structured data for printing in wrapper
return(list(
list(
major_version = major_java_ver,
output = output
))
)
}

# Internal function: Spawn subprocess to check Java with rJava - this gets cached
Expand Down
56 changes: 5 additions & 51 deletions R/java_list_available.R
Original file line number Diff line number Diff line change
Expand Up @@ -103,62 +103,16 @@ java_list_available <- function(

#' @keywords internal
list_temurin_versions_impl <- function(platform, arch) {
# Adoptium API mapping
api_os <- switch(
platform,
"macos" = "mac",
"alpine-linux" = "alpine-linux",
platform
candidates <- tryCatch(
java_valid_major_versions_temurin(platform = platform, arch = arch),
error = function(e) character(0)
)

# We first get available major releases
major_vers <- tryCatch(
java_valid_major_versions_temurin(),
error = function(e) return(NULL)
)

if (is.null(major_vers)) {
if (length(candidates) == 0) {
return(data.frame())
}

all_releases <- list()

for (v in major_vers) {
url <- sprintf(
"https://api.adoptium.net/v3/assets/feature_releases/%s/ga?os=%s&architecture=%s&image_type=jdk&jvm_impl=hotspot",
v,
api_os,
arch
)

data <- tryCatch(
read_json_url(url, max_simplify_lvl = "list"),
error = function(e) NULL
)

if (is.null(data) || length(data) == 0) {
next
}

for (rel in data) {
all_releases[[length(all_releases) + 1]] <- data.frame(
backend = "native",
vendor = "Temurin",
major = as.integer(v),
version = rel$version_data$semver,
platform = platform,
arch = arch,
identifier = rel$version_data$openjdk_version,
checksum_available = TRUE,
stringsAsFactors = FALSE
)
}
}

if (length(all_releases) == 0) {
return(data.frame())
}
do.call(rbind, all_releases)
temurin_probe_available_versions(platform, arch, candidates)$releases
}

#' @keywords internal
Expand Down
Loading
Loading