From f33b89e403358dfd0a97a9534616404d39ff1da7 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Thu, 11 Jun 2026 20:40:34 +0200 Subject: [PATCH 1/3] ci: run tests on ubuntu, macos, and windows --- .github/workflows/ci-build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b4dcfcb..54685c6 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -14,7 +14,11 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] permissions: contents: read From 03d814eae7ea986d9bc3d7ac868fadb7deadc940 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Thu, 11 Jun 2026 20:43:00 +0200 Subject: [PATCH 2/3] 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(); From 8a4d7a454c06288327cea3d85b4543802ac38d62 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Thu, 11 Jun 2026 20:44:10 +0200 Subject: [PATCH 3/3] Revert "fix: resolve path canonicalization issue in acceptFolder on macOS/Windows" This reverts commit 03d814eae7ea986d9bc3d7ac868fadb7deadc940. --- .../jdkproviders/BaseFoldersJdkProvider.java | 31 +------------------ .../jdkproviders/DefaultJdkProvider.java | 2 +- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java b/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java index 44f4c19..609c924 100644 --- a/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java +++ b/src/main/java/dev/jbang/devkitman/jdkproviders/BaseFoldersJdkProvider.java @@ -4,7 +4,6 @@ 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; @@ -131,35 +130,7 @@ protected Stream listJdkPaths() throws IOException { } protected boolean acceptFolder(@NonNull Path 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; + return jdkFolder.startsWith(jdksRoot) && JavaUtils.hasJavacCmd(jdkFolder); } 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 104bb93..0ab6476 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) && !isUnderRoot(jdkFolder)) { + if (!jdkFolder.equals(defaultJdkLink) && !jdkFolder.startsWith(jdksRoot)) { return false; } String nm = jdkFolder.getFileName().toString();