Skip to content

Commit f9623d8

Browse files
authored
revise latest package improvements (#93)
* Introduce resolve_symlinks utility; normalize JAVA_HOME and use effective_java_home for cache keys; replace repeated symlink resolution with helper in java_find; remove redundant version-sorting * improve symlink path resolution robustness, and add rJava version caching tests. * fix tests * simplify effective Java home determination logic.
1 parent b69794a commit f9623d8

4 files changed

Lines changed: 145 additions & 43 deletions

File tree

R/internal_utilities.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,34 @@ is_rjavaenv_cache_path <- function(path) {
267267
return(startsWith(path_norm, cache_norm))
268268
}
269269

270+
#' Resolve symlinks recursively on Unix systems
271+
#'
272+
#' @param path Character. Path to resolve.
273+
#' @param max_depth Integer. Maximum symlink depth to follow (default 10).
274+
#' @return Character. Resolved path (or original if not a symlink or on Windows).
275+
#' @keywords internal
276+
#' @noRd
277+
resolve_symlinks <- function(path, max_depth = 10L) {
278+
if (.Platform$OS.type != "unix" || !nzchar(path)) {
279+
return(path)
280+
}
281+
282+
real_path <- path
283+
for (i in seq_len(max_depth)) {
284+
link <- Sys.readlink(real_path)
285+
if (is.na(link) || !nzchar(link)) {
286+
break
287+
}
288+
# Handle relative symlinks
289+
if (!startsWith(link, "/")) {
290+
link <- file.path(dirname(real_path), link)
291+
}
292+
real_path <- link
293+
}
294+
295+
return(real_path)
296+
}
297+
270298
#' Internal readline wrapper for testability
271299
#'
272300
#' Wraps base::readline() to enable mocking in tests without polluting public API.

R/java_env.R

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,8 @@ java_env_set_rprofile <- function(
187187
project_path <- ifelse(is.null(project_path), getwd(), project_path)
188188
rprofile_path <- file.path(project_path, ".Rprofile")
189189

190-
# Normalize the path for Windows
191-
if (.Platform$OS.type == "windows") {
192-
java_home <- gsub("\\\\", "/", java_home)
193-
}
190+
# Normalize the path for consistency (especially on Windows)
191+
java_home <- normalizePath(java_home, winslash = "/", mustWork = FALSE)
194192

195193
lines_to_add <- c(
196194
"# rJavaEnv begin: Manage JAVA_HOME",
@@ -273,10 +271,11 @@ java_check_version_rjava <- function(
273271
}
274272

275273
# Get check result (either cached or fresh)
276-
cache_key <- Sys.getenv("JAVA_HOME")
274+
# Use the effective java_home as cache key (what we're actually checking)
275+
effective_java_home <- java_home
277276

278277
if (.use_cache) {
279-
data <- ._java_version_check_rjava_impl(java_home, cache_key)
278+
data <- ._java_version_check_rjava_impl(java_home, effective_java_home)
280279
} else {
281280
# Bypass cache - call the implementation directly
282281
data <- ._java_version_check_rjava_impl_original(java_home)
@@ -400,10 +399,15 @@ java_check_version_cmd <- function(
400399
.use_cache = FALSE
401400
) {
402401
# Get data (either cached or fresh)
403-
cache_key <- Sys.getenv("JAVA_HOME")
402+
# Use the effective java_home as cache key
403+
effective_java_home <- if (is.null(java_home)) {
404+
Sys.getenv("JAVA_HOME")
405+
} else {
406+
java_home
407+
}
404408

405409
if (.use_cache) {
406-
data <- ._java_version_check_impl(java_home, cache_key)
410+
data <- ._java_version_check_impl(java_home, effective_java_home)
407411
} else {
408412
# Bypass cache - call the implementation directly
409413
data <- ._java_version_check_impl_original(java_home)

R/java_find.R

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,7 @@
3131
# 2. PATH Lookup (Resolving Symlinks)
3232
java_bin <- Sys.which("java")
3333
if (nzchar(java_bin)) {
34-
real_path <- java_bin
35-
# Recursive symlink resolution for Linux (handles /etc/alternatives)
36-
if (.Platform$OS.type == "unix") {
37-
for (i in 1:10) {
38-
link <- Sys.readlink(real_path)
39-
if (!nzchar(link)) {
40-
break
41-
}
42-
if (!startsWith(link, "/")) {
43-
link <- file.path(dirname(real_path), link)
44-
}
45-
real_path <- link
46-
}
47-
}
34+
real_path <- resolve_symlinks(java_bin)
4835
# If path ends in /bin/java, the grandparent dir is JAVA_HOME
4936
if (grepl("[/\\\\]bin[/\\\\]java(\\.exe)?$", real_path)) {
5037
candidates <- c(candidates, dirname(dirname(real_path)))
@@ -197,13 +184,6 @@
197184
# Set is_default to FALSE - it will be calculated by the caller
198185
result_df$is_default <- FALSE
199186

200-
# 7. Sort by version (descending only, since is_default is all FALSE)
201-
sort_order <- order(
202-
-as.numeric(result_df$version),
203-
decreasing = TRUE
204-
)
205-
result_df <- result_df[sort_order, ]
206-
207187
rownames(result_df) <- NULL
208188
return(result_df)
209189
}
@@ -321,20 +301,7 @@ java_find_system <- function(quiet = TRUE, .use_cache = FALSE) {
321301
if (is.null(default_java)) {
322302
java_bin <- Sys.which("java")
323303
if (nzchar(java_bin)) {
324-
real_path <- java_bin
325-
# Recursive symlink resolution
326-
if (.Platform$OS.type == "unix") {
327-
for (i in 1:10) {
328-
link <- Sys.readlink(real_path)
329-
if (!nzchar(link)) {
330-
break
331-
}
332-
if (!startsWith(link, "/")) {
333-
link <- file.path(dirname(real_path), link)
334-
}
335-
real_path <- link
336-
}
337-
}
304+
real_path <- resolve_symlinks(java_bin)
338305
# Extract JAVA_HOME from /bin/java path
339306
if (grepl("[/\\\\]bin[/\\\\]java(\\.exe)?$", real_path)) {
340307
default_java <- normalizePath(
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
test_that("resolve_symlinks returns path as-is on non-Unix or empty path", {
2+
# We can't easily mock Windows on Linux/Mac for the OS check itself if it uses .Platform$OS.type
3+
# But we can test empty path
4+
expect_equal(rJavaEnv:::resolve_symlinks(""), "")
5+
})
6+
7+
test_that("resolve_symlinks returns original path if it is not a symlink", {
8+
skip_on_os("windows")
9+
10+
tmp <- withr::local_tempdir()
11+
target <- file.path(tmp, "target")
12+
file.create(target)
13+
14+
expect_equal(rJavaEnv:::resolve_symlinks(target), target)
15+
})
16+
17+
test_that("resolve_symlinks resolves simple symlink", {
18+
skip_on_os("windows")
19+
20+
tmp <- withr::local_tempdir()
21+
target <- file.path(tmp, "target")
22+
file.create(target)
23+
link <- file.path(tmp, "link")
24+
file.symlink(target, link)
25+
26+
expect_equal(rJavaEnv:::resolve_symlinks(link), target)
27+
})
28+
29+
test_that("resolve_symlinks resolves relative symlink", {
30+
skip_on_os("windows")
31+
32+
tmp <- withr::local_tempdir()
33+
# Use a subdirectory to ensure we are testing relative path resolution
34+
dir.create(file.path(tmp, "subdir"))
35+
36+
# Create the target file
37+
target <- file.path(tmp, "subdir", "target")
38+
file.create(target)
39+
40+
# create a symlink "link1" in subdir that points to "target" (relative)
41+
withr::with_dir(file.path(tmp, "subdir"), {
42+
file.symlink("target", "link1")
43+
})
44+
45+
link1_abs <- file.path(tmp, "subdir", "link1")
46+
47+
# verify it is a symlink
48+
expect_true(nzchar(Sys.readlink(link1_abs)))
49+
50+
# resolve it
51+
expect_equal(rJavaEnv:::resolve_symlinks(link1_abs), target)
52+
})
53+
54+
test_that("resolve_symlinks handles nested symlinks", {
55+
skip_on_os("windows")
56+
57+
tmp <- withr::local_tempdir()
58+
target <- file.path(tmp, "target")
59+
file.create(target)
60+
61+
link1 <- file.path(tmp, "link1")
62+
link2 <- file.path(tmp, "link2")
63+
link3 <- file.path(tmp, "link3")
64+
65+
file.symlink(target, link1)
66+
file.symlink(link1, link2)
67+
file.symlink(link2, link3)
68+
69+
expect_equal(rJavaEnv:::resolve_symlinks(link3), target)
70+
})
71+
72+
test_that("resolve_symlinks respects max_depth", {
73+
skip_on_os("windows")
74+
75+
tmp <- withr::local_tempdir()
76+
link1 <- file.path(tmp, "link1")
77+
link2 <- file.path(tmp, "link2")
78+
79+
# Circular reference
80+
# link1 -> link2
81+
# link2 -> link1
82+
83+
file.symlink(link2, link1)
84+
file.symlink(link1, link2)
85+
86+
# With default depth (10), it should stop eventually and return one of the links
87+
# It shouldn't hang or crash stack.
88+
89+
res <- rJavaEnv:::resolve_symlinks(link1, max_depth = 5)
90+
expect_true(res %in% c(link1, link2))
91+
})
92+
93+
test_that("resolve_symlinks handles broken links", {
94+
skip_on_os("windows")
95+
96+
tmp <- withr::local_tempdir()
97+
target <- file.path(tmp, "missing_target")
98+
link <- file.path(tmp, "link")
99+
file.symlink(target, link)
100+
101+
res <- rJavaEnv:::resolve_symlinks(link)
102+
expect_equal(res, target)
103+
})

0 commit comments

Comments
 (0)