From 89b9b4a56d8de7f4859a2efa7963bc957840a677 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Tue, 26 May 2026 18:51:13 +0200 Subject: [PATCH] feat: added Mac JDK provider --- .../jdkproviders/MacJdkProvider.java | 99 +++++++++++++++++++ .../services/dev.jbang.devkitman.JdkDiscovery | 1 + .../dev/jbang/devkitman/TestJdkProviders.java | 2 + .../jdkproviders/MacJdkProviderTest.java | 82 +++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 src/main/java/dev/jbang/devkitman/jdkproviders/MacJdkProvider.java create mode 100644 src/test/java/dev/jbang/devkitman/jdkproviders/MacJdkProviderTest.java diff --git a/src/main/java/dev/jbang/devkitman/jdkproviders/MacJdkProvider.java b/src/main/java/dev/jbang/devkitman/jdkproviders/MacJdkProvider.java new file mode 100644 index 0000000..9a8debf --- /dev/null +++ b/src/main/java/dev/jbang/devkitman/jdkproviders/MacJdkProvider.java @@ -0,0 +1,99 @@ +package dev.jbang.devkitman.jdkproviders; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +import org.jspecify.annotations.NonNull; + +import dev.jbang.devkitman.JdkDiscovery; +import dev.jbang.devkitman.JdkProvider; +import dev.jbang.devkitman.util.FileUtils; +import dev.jbang.devkitman.util.OsUtils; + +/** + * This JDK provider detects JDKs that have been installed in the standard + * location on macOS: {@code /Library/Java/JavaVirtualMachines/}. + * + *

+ * On macOS, JDKs are stored as bundles with the structure: + * {@code /Library/Java/JavaVirtualMachines/.jdk/Contents/Home} + */ +public class MacJdkProvider extends BaseFoldersJdkProvider { + private static final Path JDKS_ROOT = Paths.get("/Library/Java/JavaVirtualMachines"); + private static final String CONTENTS_HOME = "Contents/Home"; + + public MacJdkProvider() { + super(jdksRoot()); + } + + MacJdkProvider(@NonNull Path jdksRoot) { + super(jdksRoot); + } + + public static Path jdksRoot() { + return JDKS_ROOT; + } + + @Override + public @NonNull String description() { + return "The JDKs installed in /Library/Java/JavaVirtualMachines on macOS."; + } + + @Override + public boolean canUse() { + return OsUtils.isMac() && super.canUse(); + } + + @Override + @NonNull + protected Stream listJdkPaths() throws IOException { + if (Files.isDirectory(jdksRoot)) { + return Files.list(jdksRoot) + .map(bundle -> bundle.resolve(CONTENTS_HOME)) + .filter(this::acceptFolder); + } + return Stream.empty(); + } + + @Override + protected boolean acceptFolder(@NonNull Path jdkFolder) { + return super.acceptFolder(jdkFolder) && !FileUtils.isLink(jdkFolder.getParent().getParent()); + } + + @Override + public String jdkId(@NonNull Path jdkFolder) { + // jdkFolder is /.jdk/Contents/Home + // Use the bundle name (without .jdk extension) as the ID + String bundleName = jdkFolder.getParent().getParent().getFileName().toString(); + if (bundleName.endsWith(".jdk")) { + bundleName = bundleName.substring(0, bundleName.length() - 4); + } + return bundleName; + } + + @Override + @NonNull + protected Path getJdkPath(@NonNull String id) { + // Strip the provider suffix to get the bundle base name + String bundleBase = id.endsWith("-" + name()) ? id.substring(0, id.length() - name().length() - 1) : id; + return jdksRoot.resolve(bundleBase + ".jdk").resolve(CONTENTS_HOME); + } + + public static class Discovery implements JdkDiscovery { + public static final String PROVIDER_ID = "mac"; + + @Override + @NonNull + public String name() { + return PROVIDER_ID; + } + + @Override + public JdkProvider create(@NonNull Config config) { + return new MacJdkProvider(); + } + } +} diff --git a/src/main/resources/META-INF/services/dev.jbang.devkitman.JdkDiscovery b/src/main/resources/META-INF/services/dev.jbang.devkitman.JdkDiscovery index 56a02a0..8b8a140 100644 --- a/src/main/resources/META-INF/services/dev.jbang.devkitman.JdkDiscovery +++ b/src/main/resources/META-INF/services/dev.jbang.devkitman.JdkDiscovery @@ -5,6 +5,7 @@ dev.jbang.devkitman.jdkproviders.PathJdkProvider$Discovery dev.jbang.devkitman.jdkproviders.LinkedJdkProvider$Discovery dev.jbang.devkitman.jdkproviders.JBangJdkProvider$Discovery dev.jbang.devkitman.jdkproviders.LinuxJdkProvider$Discovery +dev.jbang.devkitman.jdkproviders.MacJdkProvider$Discovery dev.jbang.devkitman.jdkproviders.MiseJdkProvider$Discovery dev.jbang.devkitman.jdkproviders.MultiHomeJdkProvider$Discovery dev.jbang.devkitman.jdkproviders.ScoopJdkProvider$Discovery diff --git a/src/test/java/dev/jbang/devkitman/TestJdkProviders.java b/src/test/java/dev/jbang/devkitman/TestJdkProviders.java index 2fd4bcf..44b2015 100644 --- a/src/test/java/dev/jbang/devkitman/TestJdkProviders.java +++ b/src/test/java/dev/jbang/devkitman/TestJdkProviders.java @@ -34,6 +34,7 @@ void testAllNames() { "linked", "jbang", "linux", + "mac", "mise", "multihome", "scoop", @@ -76,6 +77,7 @@ void testAll() { instanceOf(LinkedJdkProvider.class), instanceOf(JBangJdkProvider.class), instanceOf(LinuxJdkProvider.class), + instanceOf(MacJdkProvider.class), instanceOf(MiseJdkProvider.class), instanceOf(MultiHomeJdkProvider.class), instanceOf(ScoopJdkProvider.class), diff --git a/src/test/java/dev/jbang/devkitman/jdkproviders/MacJdkProviderTest.java b/src/test/java/dev/jbang/devkitman/jdkproviders/MacJdkProviderTest.java new file mode 100644 index 0000000..ffbeb1d --- /dev/null +++ b/src/test/java/dev/jbang/devkitman/jdkproviders/MacJdkProviderTest.java @@ -0,0 +1,82 @@ +package dev.jbang.devkitman.jdkproviders; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import dev.jbang.devkitman.BaseTest; +import dev.jbang.devkitman.Jdk; + +public class MacJdkProviderTest extends BaseTest { + + private Path createMockMacJdkBundle(Path jvmRoot, String bundleName, int version) throws IOException { + Path bundlePath = jvmRoot.resolve(bundleName + ".jdk"); + Path home = bundlePath.resolve("Contents/Home"); + Files.createDirectories(home); + initMockJdkDir(home, version + ".0.7"); + return home; + } + + @Test + void testMacProviderFindsInstalledJdks() throws IOException { + Path jvmRoot = config.cachePath().resolve("JavaVirtualMachines"); + Files.createDirectories(jvmRoot); + + createMockMacJdkBundle(jvmRoot, "temurin-17", 17); + createMockMacJdkBundle(jvmRoot, "temurin-21", 21); + + MacJdkProvider provider = new MacJdkProvider(jvmRoot); + + List installed = provider.listInstalled().collect(Collectors.toList()); + + assertThat(installed, hasSize(2)); + List ids = installed.stream().map(Jdk::id).toList(); + assertThat(ids.contains("temurin-17"), is(true)); + assertThat(ids.contains("temurin-21"), is(true)); + } + + @Test + void testMacProviderIgnoresJreOnlyBundles() throws IOException { + Path jvmRoot = config.cachePath().resolve("JavaVirtualMachines"); + Files.createDirectories(jvmRoot); + + Path home21 = createMockMacJdkBundle(jvmRoot, "temurin-21", 21); + + // Create a JRE bundle (no javac) + Path jreBundle = jvmRoot.resolve("corretto-jre-11.jre"); + Path jreHome = jreBundle.resolve("Contents/Home"); + Files.createDirectories(jreHome); + initMockJdkDir(jreHome, "11.0.7", "JAVA_RUNTIME_VERSION", false, false, false, false); + + MacJdkProvider provider = new MacJdkProvider(jvmRoot); + + List installed = provider.listInstalled().collect(Collectors.toList()); + + assertThat(installed, hasSize(1)); + assertThat(installed.get(0).id(), is("temurin-21")); + assertThat(installed.get(0).home(), is(home21)); + } + + @Test + void testMacProviderJdkId() throws IOException { + Path jvmRoot = config.cachePath().resolve("JavaVirtualMachines"); + Files.createDirectories(jvmRoot); + + createMockMacJdkBundle(jvmRoot, "zulu-17.0.10", 17); + + MacJdkProvider provider = new MacJdkProvider(jvmRoot); + + List installed = provider.listInstalled().collect(Collectors.toList()); + + assertThat(installed, hasSize(1)); + assertThat(installed.get(0).id(), is("zulu-17.0.10")); + } +}