From a45afdf1775ac517a9390d47ce061c514b1d42c6 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Thu, 11 Jun 2026 20:43:00 +0200 Subject: [PATCH] fix: resolve path canonicalization issue in acceptFolder on macOS/Windows On macOS, temp directories resolve through /private/var (e.g. /var/folders/... -> /private/var/folders/...) causing jdkFolder.startsWith(jdksRoot) to return false when the jdkFolder path has been resolved via toRealPath() but jdksRoot has not. This caused LinkedJdk.linked() to fall through to ExternalJdkProvider, producing external- ids instead of the correct provider id. The fix adds an isUnderRoot() helper that falls back to comparing real (canonical) paths when the simple startsWith check fails. The resolved real root path is cached for performance. Fixes jbangdev/jbang#2515 test failures on macOS and Windows. --- .../jdkproviders/BaseFoldersJdkProvider.java | 31 ++++++++++++++++++- .../jdkproviders/DefaultJdkProvider.java | 2 +- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java b/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java index 609c924..44f4c19 100644 --- a/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java +++ b/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; @@ -130,7 +131,35 @@ protected Stream listJdkPaths() throws IOException { } protected boolean acceptFolder(@NonNull Path jdkFolder) { - return jdkFolder.startsWith(jdksRoot) && JavaUtils.hasJavacCmd(jdkFolder); + return isUnderRoot(jdkFolder) && JavaUtils.hasJavacCmd(jdkFolder); + } + + /** + * Checks if the given path is under the jdksRoot directory. This method handles + * the case where the paths may have different canonical forms (e.g. /var vs + * /private/var on macOS) by falling back to toRealPath() comparison when a + * simple startsWith check fails. + */ + protected boolean isUnderRoot(@NonNull Path jdkFolder) { + if (jdkFolder.startsWith(jdksRoot)) { + return true; + } + try { + return jdkFolder.toRealPath().startsWith(realJdksRoot()); + } catch (IOException e) { + return false; + } + } + + private final AtomicReference realJdksRootCache = new AtomicReference<>(); + + private Path realJdksRoot() throws IOException { + Path cached = realJdksRootCache.get(); + if (cached == null) { + cached = jdksRoot.toRealPath(); + realJdksRootCache.set(cached); + } + return cached; } private final Pattern validId = Pattern.compile("^[a-zA-Z0-9._+-]+$"); diff --git a/src/main/java/dev/jbang/devkitman/jdkproviders/DefaultJdkProvider.java b/src/main/java/dev/jbang/devkitman/jdkproviders/DefaultJdkProvider.java index 0ab6476..104bb93 100644 --- a/src/main/java/dev/jbang/devkitman/jdkproviders/DefaultJdkProvider.java +++ b/src/main/java/dev/jbang/devkitman/jdkproviders/DefaultJdkProvider.java @@ -158,7 +158,7 @@ protected Path getJdkPath(@NonNull String id) { } protected boolean acceptFolder(@NonNull Path jdkFolder) { - if (!jdkFolder.equals(defaultJdkLink) && !jdkFolder.startsWith(jdksRoot)) { + if (!jdkFolder.equals(defaultJdkLink) && !isUnderRoot(jdkFolder)) { return false; } String nm = jdkFolder.getFileName().toString();