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
99 changes: 99 additions & 0 deletions src/main/java/dev/jbang/devkitman/jdkproviders/MacJdkProvider.java
Original file line number Diff line number Diff line change
@@ -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/}.
*
* <p>
* On macOS, JDKs are stored as bundles with the structure:
* {@code /Library/Java/JavaVirtualMachines/<name>.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<Path> 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 <jdksRoot>/<bundle>.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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/dev/jbang/devkitman/TestJdkProviders.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void testAllNames() {
"linked",
"jbang",
"linux",
"mac",
"mise",
"multihome",
"scoop",
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Jdk.InstalledJdk> installed = provider.listInstalled().collect(Collectors.toList());

assertThat(installed, hasSize(2));
List<String> 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<Jdk.InstalledJdk> 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<Jdk.InstalledJdk> installed = provider.listInstalled().collect(Collectors.toList());

assertThat(installed, hasSize(1));
assertThat(installed.get(0).id(), is("zulu-17.0.10"));
}
}