Add support to detect installed Java versions using Mac native and SDKman.
Closes #196
This commit is contained in:
@@ -67,6 +67,12 @@
|
||||
<version>1.4.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.plist</groupId>
|
||||
<artifactId>dd-plist</artifactId>
|
||||
<version>1.23</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.RegexFileFilter;
|
||||
|
||||
import org.springframework.boot.system.SystemProperties;
|
||||
import org.springframework.data.release.model.JavaVersion;
|
||||
import org.springframework.data.release.model.Version;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import com.dd.plist.NSArray;
|
||||
import com.dd.plist.NSDictionary;
|
||||
import com.dd.plist.XMLPropertyListParser;
|
||||
|
||||
/**
|
||||
* Utility to detect a Java runtime version.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class JavaRuntimes {
|
||||
|
||||
private static final List<JdkDetector> DETECTORS = Arrays.asList(new SDKmanJdkDetector(), new MacNativeJdkDetector());
|
||||
private static final Lazy<List<JdkInstallation>> JDKS = Lazy.of(() -> {
|
||||
|
||||
List<JdkInstallation> jdks = DETECTORS.stream().flatMap(it -> it.detect().stream()).sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Collections.reverse(jdks);
|
||||
|
||||
return Collections.unmodifiableList(jdks);
|
||||
});
|
||||
|
||||
/**
|
||||
* Lookup a {@link JdkInstallation} by detecting installed JDKs and applying the {@link Predicate filter}. Returns the
|
||||
* first matching one or throws {@link NoSuchElementException}.
|
||||
*
|
||||
* @param filter
|
||||
* @return
|
||||
*/
|
||||
public static JdkInstallation getJdk(Predicate<JdkInstallation> filter) {
|
||||
return getJdk(filter, () -> "Cannot obtain required JDK");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a {@link JdkInstallation} by detecting installed JDKs and applying the {@link Predicate filter}. Returns the
|
||||
* first matching one or throws {@link NoSuchElementException}.
|
||||
*
|
||||
* @param filter
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public static JdkInstallation getJdk(Predicate<JdkInstallation> filter, Supplier<String> message) {
|
||||
|
||||
List<JdkInstallation> jdks = JDKS.get();
|
||||
|
||||
return jdks.stream().filter(filter).findFirst()
|
||||
.orElseThrow(() -> new NoSuchElementException(String.format("%s%nAvailable JDK: %s", message.get(), jdks)));
|
||||
}
|
||||
|
||||
/**
|
||||
* JDK detection strategy.
|
||||
*/
|
||||
interface JdkDetector {
|
||||
|
||||
/**
|
||||
* @return {@code true} if the detector strategy is available.
|
||||
*/
|
||||
boolean isAvailable();
|
||||
|
||||
/**
|
||||
* @return a list of JDK installations.
|
||||
*/
|
||||
List<JdkInstallation> detect();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector to determine a {@link JdkInstallation}.
|
||||
*/
|
||||
public static class Selector {
|
||||
|
||||
private String notFoundMessage;
|
||||
private Predicate<JdkInstallation> predicate;
|
||||
|
||||
private Selector() {
|
||||
|
||||
}
|
||||
|
||||
public static Selector builder() {
|
||||
return new Selector();
|
||||
}
|
||||
|
||||
public static Selector from(JavaVersion javaVersion) {
|
||||
return builder().and(it -> javaVersion.getVersionDetector().test(it.getVersion()))
|
||||
.message("Cannot find Java " + javaVersion.getName());
|
||||
}
|
||||
|
||||
public Selector and(Predicate<JdkInstallation> predicate) {
|
||||
this.predicate = this.predicate == null ? predicate : this.predicate.and(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Selector notGraalVM() {
|
||||
this.notFoundMessage += " (Not GraalVM)";
|
||||
return and(it -> !it.getName().contains("GraalVM"));
|
||||
}
|
||||
|
||||
public Selector message(String notFoundMessage) {
|
||||
this.notFoundMessage = notFoundMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JdkInstallation getRequiredJdkInstallation() {
|
||||
return JavaRuntimes.getJdk(predicate, () -> notFoundMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Detector using the SDKman utility storing Java installations in {@code ~/.sdkman/candidates/java}.
|
||||
*/
|
||||
static class SDKmanJdkDetector implements JdkDetector {
|
||||
|
||||
private static final File sdkManJavaHome = new File(FileUtils.getUserDirectoryPath(), ".sdkman/candidates/java");
|
||||
|
||||
private static final Pattern CANDIDATE = Pattern.compile("(\\d+[\\.\\d+]+)[.-][-a-zA-Z]*");
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return sdkManJavaHome.exists() && sdkManJavaHome.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JdkInstallation> detect() {
|
||||
|
||||
File[] files = sdkManJavaHome.listFiles((FileFilter) new RegexFileFilter(CANDIDATE));
|
||||
|
||||
return Arrays.stream(files).map(it -> {
|
||||
|
||||
Matcher matcher = CANDIDATE.matcher(it.getName());
|
||||
matcher.find();
|
||||
|
||||
return new JdkInstallation(Version.parse(matcher.group(1)), it.getName(), it);
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detector using the {@code /usr/libexec/java_home} utility storing Java installations in {@code /Libraries/Java} on
|
||||
* the Mac.
|
||||
*/
|
||||
static class MacNativeJdkDetector implements JdkDetector {
|
||||
|
||||
private static final File javaHomeBinary = new File("/usr/libexec/java_home");
|
||||
|
||||
private static final Pattern VERSION = Pattern.compile("(\\d+(:?\\.\\d+)*)(:?_\\+.*)?");
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return javaHomeBinary.exists() && SystemProperties.get("os.name").contains("Mac");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public List<JdkInstallation> detect() {
|
||||
|
||||
Process process = new ProcessBuilder(javaHomeBinary.toString(), "-X").redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||
.start();
|
||||
|
||||
process.waitFor(5, TimeUnit.SECONDS);
|
||||
byte[] out = StreamUtils.copyToByteArray(process.getInputStream());
|
||||
|
||||
if (process.exitValue() != 0) {
|
||||
|
||||
throw new IllegalStateException(javaHomeBinary + " failed with: " + System.lineSeparator() + new String(out)
|
||||
+ new String(StreamUtils.copyToByteArray(process.getErrorStream())));
|
||||
}
|
||||
|
||||
NSArray array = (NSArray) XMLPropertyListParser.parse(new ByteArrayInputStream(out));
|
||||
|
||||
return Arrays.stream(array.getArray()).map(it -> {
|
||||
|
||||
NSDictionary dict = (NSDictionary) it;
|
||||
|
||||
String jvmHomePath = dict.get("JVMHomePath").toJavaObject(String.class);
|
||||
String name = dict.get("JVMName").toJavaObject(String.class);
|
||||
String version = dict.get("JVMVersion").toJavaObject(String.class).replace('_', '.');
|
||||
|
||||
Matcher matcher = VERSION.matcher(version);
|
||||
matcher.find();
|
||||
|
||||
return new JdkInstallation(Version.parse(matcher.group(1)), name, new File(jvmHomePath));
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class JdkInstallation implements Comparable<JdkInstallation> {
|
||||
|
||||
Version version;
|
||||
String name;
|
||||
File home;
|
||||
|
||||
@Override
|
||||
public int compareTo(JdkInstallation o) {
|
||||
return this.version.compareTo(o.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.release.model.Version;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JavaRuntimes}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class JdkUtilityTests {
|
||||
|
||||
@Test
|
||||
void shouldDiscoverCurrentJavaVersion() {
|
||||
|
||||
Version currentVersion = Version.parse(System.getProperty("java.version").replace('_', '.'));
|
||||
JavaRuntimes.JdkInstallation jdk = JavaRuntimes.getJdk(it -> it.getVersion().is(currentVersion));
|
||||
|
||||
assertThat(jdk).isNotNull();
|
||||
assertThat(System.getProperty("java.home")).contains(jdk.getHome().getPath());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user