Add MavenRuntime detector.

Support SDKman, MAVEN_HOME, Maven Wrapper and a provided maven home File.
This commit is contained in:
Mark Paluch
2024-11-11 09:14:47 +01:00
parent 10b25ee74b
commit 37b7444010
11 changed files with 900 additions and 370 deletions

View File

@@ -49,4 +49,5 @@ class BuildConfiguration {
return new XBProjector(config, Flags.TO_STRING_RENDERS_XML);
}
}

View File

@@ -20,7 +20,11 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -36,7 +40,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
*/
@Value
class CommandLine {
public class CommandLine {
@NonNull List<Goal> goals;
@NonNull List<Argument> arguments;

View File

@@ -17,59 +17,32 @@ package org.springframework.data.release.build;
import static org.springframework.data.release.build.CommandLine.Argument.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.JavaVersion;
import org.springframework.data.release.model.Named;
import org.springframework.data.release.model.SupportedProject;
import org.springframework.data.release.utils.Logger;
import org.springframework.lang.Nullable;
import org.springframework.shell.support.util.StringUtils;
import org.springframework.stereotype.Component;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Slf4j
@Component
public class MavenRuntime {
public class MavenRuntime extends MavenRuntimeSupport {
private static final Pattern versionPattern = Pattern.compile("Apache Maven ((\\d\\.?)+) \\(.*\\)");
private final Workspace workspace;
private final Logger logger;
private final MavenProperties properties;
private final JavaRuntimes.JdkInstallation jdk;
/**
* Creates a new {@link MavenRuntime} for the given {@link Workspace} and Maven home.
@@ -78,82 +51,24 @@ public class MavenRuntime {
* @param logger must not be {@literal null}.
* @param properties must not be {@literal null}.
*/
@Autowired
public MavenRuntime(Workspace workspace, Logger logger, MavenProperties properties) {
this(workspace, logger, properties, JavaVersion.VERSION_1_8);
public MavenRuntime(Workspace workspace, Logger logger, MavenRuntimes.MavenInstallation mavenInstallation,
MavenProperties properties) {
this(workspace, logger, mavenInstallation.getHome(), properties, JavaVersion.VERSION_1_8);
}
private MavenRuntime(Workspace workspace, Logger logger, MavenProperties properties,
private MavenRuntime(Workspace workspace, Logger logger, File mavenHome, MavenProperties properties,
JavaVersion requiredJavaVersion) {
super(mavenHome, properties.getLocalRepository(),
JavaRuntimes.Selector.from(requiredJavaVersion).notGraalVM().getRequiredJdkInstallation());
this.workspace = workspace;
this.logger = logger;
this.properties = properties;
this.jdk = JavaRuntimes.Selector.from(requiredJavaVersion).notGraalVM().getRequiredJdkInstallation();
logger.log("Maven", "Using" + jdk + " as default Java Runtime");
logger.log("Maven", "Using " + getJdk() + " as default Java Runtime");
}
public MavenRuntime withJavaVersion(JavaVersion javaVersion) {
return new MavenRuntime(workspace, logger, properties, javaVersion);
}
@SneakyThrows
public String getVersion() throws IllegalStateException {
String version = detectBuildPropertiesVersion();
return version != null ? version : runVersionCommand();
}
@Nullable
@SneakyThrows
private String detectBuildPropertiesVersion() {
File libs = new File(properties.getMavenHome(), "lib");
File[] files = libs.listFiles((FileFilter) new PrefixFileFilter("maven-core-"));
if (files == null || files.length != 1) {
return null;
}
try (ZipFile zipFile = new ZipFile(files[0])) {
ZipEntry entry = zipFile.getEntry("org/apache/maven/messages/build.properties");
if (entry == null) {
return null;
}
Properties properties = new Properties();
try (InputStream inputStream = zipFile.getInputStream(entry)) {
properties.load(inputStream);
}
return properties.getProperty("version");
}
}
private String runVersionCommand() throws MavenInvocationException {
StringBuilder builder = new StringBuilder();
Invoker invoker = new DefaultInvoker();
invoker.setMavenHome(properties.getMavenHome());
invoker.setErrorHandler(builder::append);
invoker.setOutputHandler(builder::append);
doWithMaven(invoker, mvn -> {
mvn.setShowVersion(true);
mvn.setGoals(Collections.emptyList());
});
Matcher matcher = versionPattern.matcher(builder);
boolean foundVersion = matcher.find();
if (!foundVersion) {
throw new IllegalStateException("Cannot determine Maven Version: " + builder);
}
return matcher.group(1);
return new MavenRuntime(workspace, logger, getMavenHome(), properties, javaVersion);
}
public MavenInvocationResult execute(SupportedProject project, CommandLine arguments) {
@@ -163,14 +78,14 @@ public class MavenRuntime {
try (MavenLogger mavenLogger = getLogger(project, arguments.getGoals())) {
Invoker invoker = new DefaultInvoker();
invoker.setMavenHome(properties.getMavenHome());
invoker.setMavenHome(getMavenHome());
invoker.setOutputHandler(mavenLogger::info);
invoker.setErrorHandler(mavenLogger::warn);
InvocationResult result = doWithMaven(invoker, mvn -> {
mvn.setBaseDirectory(workspace.getProjectDirectory(project));
mavenLogger.info(String.format("Java Home: %s", jdk));
mavenLogger.info(String.format("Java Home: %s", getJavaHome()));
mavenLogger.info(String.format("Executing: mvn %s", arguments));
CommandLine disabledGradleBuildCache = arguments.and(arg("gradle.cache.local.enabled=false"))
@@ -198,31 +113,8 @@ public class MavenRuntime {
}
}
private InvocationResult doWithMaven(Invoker invoker, Consumer<InvocationRequest> mvn)
throws MavenInvocationException {
File localRepository = properties.getLocalRepository();
if (localRepository != null) {
invoker.setLocalRepositoryDirectory(localRepository);
}
File javaHome = getJavaHome();
InvocationRequest request = new DefaultInvocationRequest();
request.setJavaHome(javaHome);
request.setShellEnvironmentInherited(true);
request.setBatchMode(true);
mvn.accept(request);
return invoker.execute(request);
}
private File getJavaHome() {
return jdk.getHome().getAbsoluteFile();
}
private MavenLogger getLogger(Named project, List<CommandLine.Goal> goals) {
@Override
MavenLogger getLogger(Named project, List<CommandLine.Goal> goals) {
if (this.properties.isConsoleLogger()) {
return new SlfLogger(log, project);
@@ -231,114 +123,4 @@ public class MavenRuntime {
return new FileLogger(log, project, this.workspace.getLogsDirectory(), goals);
}
public static class MavenInvocationResult {
private final List<String> log = new ArrayList<>();
public List<String> getLog() {
return log;
}
}
/**
* Maven Logging Forwarder.
*/
interface MavenLogger extends Closeable {
void info(String message);
void warn(String message);
List<String> getLines();
}
@RequiredArgsConstructor
static class SlfLogger implements MavenLogger {
private final org.slf4j.Logger logger;
private final String logPrefix;
private final List<String> contents;
SlfLogger(org.slf4j.Logger logger, Named project) {
this.logger = logger;
this.logPrefix = StringUtils.padRight(project.getName(), 10);
this.contents = new ArrayList<>();
}
@Override
public void info(String message) {
String msg = logPrefix + ": " + message;
contents.add(msg);
logger.info(msg);
}
@Override
public void warn(String message) {
String msg = logPrefix + ": " + message;
contents.add(msg);
logger.warn(msg);
}
@Override
public void close() throws IOException {
// no-op
}
@Override
public List<String> getLines() {
return contents;
}
}
static class FileLogger implements MavenLogger {
private final PrintWriter printWriter;
private final FileOutputStream outputStream;
private final List<String> contents = new ArrayList<>();
FileLogger(org.slf4j.Logger logger, Named project, File logsDirectory, List<CommandLine.Goal> goals) {
if (!logsDirectory.exists()) {
logsDirectory.mkdirs();
}
String goalNames = goals.stream().map(CommandLine.Goal::getGoal).collect(Collectors.joining("-"));
String filename = String.format("mvn-%s-%s.log", project.getName(), goalNames).replace(':', '.');
try {
File file = new File(logsDirectory, filename);
logger.info("Routing Maven output to " + file.getCanonicalPath());
outputStream = new FileOutputStream(file, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
printWriter = new PrintWriter(outputStream, true);
}
@Override
public void info(String message) {
printWriter.println(message);
contents.add(message);
}
@Override
public void warn(String message) {
printWriter.println(message);
contents.add(message);
}
@Override
public void close() throws IOException {
printWriter.close();
outputStream.close();
}
@Override
public List<String> getLines() {
return contents;
}
}
}

View File

@@ -0,0 +1,281 @@
/*
* Copyright 2015-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.build;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.model.Named;
import org.springframework.lang.Nullable;
import org.springframework.shell.support.util.StringUtils;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Slf4j
public class MavenRuntimeSupport {
private static final Pattern versionPattern = Pattern.compile("Apache Maven ((\\d\\.?)+) \\(.*\\)");
private final File mavenHome;
private final @Nullable File localRepository;
private final JavaRuntimes.JdkInstallation jdk;
/**
* Creates a new {@link MavenRuntimeSupport}
*
* @param mavenHome
* @param localRepository
* @param jdk
*/
public MavenRuntimeSupport(File mavenHome, @Nullable File localRepository, JavaRuntimes.JdkInstallation jdk) {
this.mavenHome = mavenHome;
this.localRepository = localRepository;
this.jdk = jdk;
}
JavaRuntimes.JdkInstallation getJdk() {
return jdk;
}
File getMavenHome() {
return mavenHome;
}
@SneakyThrows
public String getVersion() throws IllegalStateException {
String version = detectBuildPropertiesVersion();
return version != null ? version : runVersionCommand();
}
@Nullable
@SneakyThrows
private String detectBuildPropertiesVersion() {
File libs = new File(mavenHome, "lib");
File[] files = libs.listFiles((FileFilter) new PrefixFileFilter("maven-core-"));
if (files == null || files.length != 1) {
return null;
}
try (ZipFile zipFile = new ZipFile(files[0])) {
ZipEntry entry = zipFile.getEntry("org/apache/maven/messages/build.properties");
if (entry == null) {
return null;
}
Properties properties = new Properties();
try (InputStream inputStream = zipFile.getInputStream(entry)) {
properties.load(inputStream);
}
return properties.getProperty("version");
}
}
private String runVersionCommand() throws MavenInvocationException {
StringBuilder builder = new StringBuilder();
Invoker invoker = new DefaultInvoker();
invoker.setMavenHome(mavenHome);
invoker.setErrorHandler(builder::append);
invoker.setOutputHandler(builder::append);
doWithMaven(invoker, mvn -> {
mvn.setShowVersion(true);
mvn.setGoals(Collections.emptyList());
});
Matcher matcher = versionPattern.matcher(builder);
boolean foundVersion = matcher.find();
if (!foundVersion) {
throw new IllegalStateException("Cannot determine Maven Version: " + builder);
}
return matcher.group(1);
}
protected InvocationResult doWithMaven(Invoker invoker, Consumer<InvocationRequest> mvn)
throws MavenInvocationException {
if (this.localRepository != null) {
invoker.setLocalRepositoryDirectory(this.localRepository);
}
File javaHome = getJavaHome();
InvocationRequest request = new DefaultInvocationRequest();
request.setJavaHome(javaHome);
request.setShellEnvironmentInherited(true);
request.setBatchMode(true);
mvn.accept(request);
return invoker.execute(request);
}
File getJavaHome() {
return jdk.getHome().getAbsoluteFile();
}
MavenLogger getLogger(Named project, List<CommandLine.Goal> goals) {
return new SlfLogger(log, project);
}
public static class MavenInvocationResult {
private final List<String> log = new ArrayList<>();
public List<String> getLog() {
return log;
}
}
/**
* Maven Logging Forwarder.
*/
interface MavenLogger extends Closeable {
void info(String message);
void warn(String message);
List<String> getLines();
}
@RequiredArgsConstructor
static class SlfLogger implements MavenLogger {
private final org.slf4j.Logger logger;
private final String logPrefix;
private final List<String> contents;
SlfLogger(org.slf4j.Logger logger, Named project) {
this.logger = logger;
this.logPrefix = StringUtils.padRight(project.getName(), 10);
this.contents = new ArrayList<>();
}
@Override
public void info(String message) {
String msg = logPrefix + ": " + message;
contents.add(msg);
logger.info(msg);
}
@Override
public void warn(String message) {
String msg = logPrefix + ": " + message;
contents.add(msg);
logger.warn(msg);
}
@Override
public void close() throws IOException {
// no-op
}
@Override
public List<String> getLines() {
return contents;
}
}
static class FileLogger implements MavenLogger {
private final PrintWriter printWriter;
private final FileOutputStream outputStream;
private final List<String> contents = new ArrayList<>();
FileLogger(org.slf4j.Logger logger, Named project, File logsDirectory, List<CommandLine.Goal> goals) {
if (!logsDirectory.exists()) {
logsDirectory.mkdirs();
}
String goalNames = goals.stream().map(CommandLine.Goal::getGoal).collect(Collectors.joining("-"));
String filename = String.format("mvn-%s-%s.log", project.getName(), goalNames).replace(':', '.');
try {
File file = new File(logsDirectory, filename);
logger.info("Routing Maven output to " + file.getCanonicalPath());
outputStream = new FileOutputStream(file, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
printWriter = new PrintWriter(outputStream, true);
}
@Override
public void info(String message) {
printWriter.println(message);
contents.add(message);
}
@Override
public void warn(String message) {
printWriter.println(message);
contents.add(message);
}
@Override
public void close() throws IOException {
printWriter.close();
outputStream.close();
}
@Override
public List<String> getLines() {
return contents;
}
}
}

View File

@@ -0,0 +1,415 @@
/*
* Copyright 2024 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
*
* http://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.build;
import lombok.Value;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
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.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.model.Version;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Utility to detect a Java runtime version.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public class MavenRuntimes {
private static final List<MavenDetector> DETECTORS = Arrays.asList(new SDKmanJdkDetector(),
new MavenWrapperDetector(), new MavenHomeEnvironmentDetector());
private final List<MavenDetector> detectors;
public MavenRuntimes(MavenDetector... detectors) {
this.detectors = new ArrayList<>(DETECTORS);
this.detectors.addAll(Arrays.asList(detectors));
}
public static MavenDetector detector(File mavenHome) {
return new MavenHomeJdkDetectorSupport(mavenHome);
}
/**
* Lookup a {@link MavenInstallation} by detecting installed Maven installations and applying the {@link Predicate
* filter}. Returns the first matching one {@code null}
*/
@Nullable
public MavenInstallation findMavenInstallation(JavaRuntimes.JdkInstallation jdk,
Predicate<MavenInstallation> filter) {
List<MavenInstallation> jdks = getMavenInstallations(jdk);
return jdks.stream().filter(filter).findFirst().orElse(null);
}
/**
* Lookup a {@link MavenInstallation} by detecting installed Maven installations and applying the {@link Predicate
* filter}. Returns the first matching one or throws {@link NoSuchElementException}.
*/
public MavenInstallation getRequiredMaven(JavaRuntimes.JdkInstallation jdk, Predicate<MavenInstallation> filter,
String runtimeName, Supplier<String> message) {
List<MavenInstallation> jdks = getMavenInstallations(jdk);
return jdks.stream().filter(filter).findFirst().orElseThrow(() -> new NoSuchMavenRuntimeException(
String.format("%s%nAvailable Maven: %s", message.get(), jdks), jdks, runtimeName));
}
public List<MavenInstallation> getMavenInstallations(JavaRuntimes.JdkInstallation jdk) {
return DETECTORS.stream() //
.filter(MavenDetector::isAvailable) //
.flatMap(it -> it.detect(jdk).stream()) //
.sorted() //
.collect(Collectors.toList());
}
static boolean isDirectory(File file) {
return file.exists() && file.isDirectory();
}
/**
* Maven detection strategy.
*/
public interface MavenDetector {
/**
* @return {@code true} if the detector strategy is available.
*/
boolean isAvailable();
/**
* @return a list of Maven installations.
*/
List<MavenInstallation> detect(JavaRuntimes.JdkInstallation jdk);
}
/**
* Selector to determine a {@link MavenInstallation}.
*/
public static class Selector {
private final MavenDetector[] detectors;
private String notFoundMessage;
private String mavenRuntimeName;
private Predicate<MavenInstallation> predicate;
private Selector(MavenDetector[] detectors) {
this.detectors = detectors;
}
public static Selector builder(MavenDetector... detectors) {
return new Selector(detectors);
}
public Selector version(Version mavenVersion) {
return and(it -> it.version.equals(mavenVersion)).name("Maven version " + mavenVersion)
.message("Cannot find required Maven version " + mavenVersion);
}
public Selector and(Predicate<MavenInstallation> predicate) {
this.predicate = this.predicate == null ? predicate : this.predicate.and(predicate);
return this;
}
public Selector message(String notFoundMessage) {
this.notFoundMessage = notFoundMessage;
return this;
}
public Selector name(String mavenRuntimeName) {
this.mavenRuntimeName = mavenRuntimeName;
return this;
}
public MavenInstallation getRequiredMavenInstallation(JavaRuntimes.JdkInstallation jdk) {
return new MavenRuntimes(detectors).getRequiredMaven(jdk, predicate, mavenRuntimeName, () -> notFoundMessage);
}
}
/**
* Detector using the SDKman utility storing Java installations in {@code ~/.sdkman/candidates/maven}.
*/
static class SDKmanJdkDetector implements MavenDetector {
private static final File sdkManMavenHome;
private static final Pattern CANDIDATE = Pattern.compile("(\\d+[\\.\\d+]+)");
static {
if (System.getenv().containsKey("SDKMAN_CANDIDATES_DIR")) {
sdkManMavenHome = new File(System.getenv().get("SDKMAN_CANDIDATES_DIR"), "maven");
} else if (System.getenv().containsKey("SDKMAN_DIR")) {
sdkManMavenHome = new File(System.getenv().get("SDKMAN_DIR"), "candidates/maven");
} else {
sdkManMavenHome = new File(FileUtils.getUserDirectoryPath(), ".sdkman/candidates/maven");
}
}
@Override
public boolean isAvailable() {
return isDirectory(sdkManMavenHome);
}
@Override
public List<MavenInstallation> detect(JavaRuntimes.JdkInstallation jdk) {
File[] files = sdkManMavenHome.listFiles((FileFilter) new RegexFileFilter(CANDIDATE));
return Arrays.stream(files).map(it -> {
Matcher matcher = CANDIDATE.matcher(it.getName());
if (!matcher.find()) {
throw new IllegalArgumentException("Cannot determine Maven version number from SDKman candidate name "
+ it.getName() + ". This should not happen in an ideal world, check the CANDIDATE regex.");
}
String candidateVersion = matcher.group(1);
Version version = Version.parse(candidateVersion);
return new MavenInstallation(version, it);
}).collect(Collectors.toList());
}
}
/**
* Detector Maven wrapper installations in {@code ~/.m2/wrapper/dists}.
*/
static class MavenWrapperDetector implements MavenDetector {
private static final Pattern CANDIDATE = Pattern.compile("apache-maven-((:?\\d+(:?\\.\\d+)*)(:?_+\\d+)?)-bin");
private static final String userHome = System.getProperty("user.home");
private static final File dists = new File(userHome, ".m2/wrapper/dists");
@Override
public boolean isAvailable() {
return isDirectory(dists);
}
@Override
public List<MavenInstallation> detect(JavaRuntimes.JdkInstallation jdk) {
File[] files = dists.listFiles((FileFilter) new RegexFileFilter(CANDIDATE));
class WrapperCandidate {
final File home;
final File hash;
final File realHome;
final Version version;
public WrapperCandidate(File home, File hash, Version version, File realHome) {
this.home = home;
this.hash = hash;
this.realHome = realHome;
this.version = version;
}
}
return Arrays.stream(files).map(it -> {
File[] hashes = it.listFiles();
File hash = hashes != null && hashes.length == 1 ? hashes[0] : null;
Matcher matcher = CANDIDATE.matcher(it.getName());
if (!matcher.find()) {
throw new IllegalArgumentException("Cannot determine Maven version number from Maven Wrapper candidate name "
+ it.getName() + ". This should not happen in an ideal world, check the CANDIDATE regex.");
}
String candidateVersion = matcher.group(1);
Version version = Version.parse(candidateVersion);
return new WrapperCandidate(it, hash, version, hash != null ? new File(hash, "apache-maven-" + version) : null);
}).filter(it -> {
if (it.hash == null) {
return false;
}
return isDirectory(it.realHome);
}).map(it -> new MavenInstallation(it.version, it.realHome)).collect(Collectors.toList());
}
}
/**
* Detector using the {@code java.home} system property.
*/
static class MavenHomeEnvironmentDetector implements MavenDetector {
private static final String maven_home_property = System.getProperty("maven.home");
private static final @Nullable MavenHomeJdkDetectorSupport mavenHome = StringUtils.hasText(maven_home_property)
? new MavenHomeJdkDetectorSupport(new File(maven_home_property))
: null;
private static final String MAVEN_HOME_ENV = System.getenv("MAVEN_HOME");
private static final @Nullable MavenHomeJdkDetectorSupport MAVEN_HOME = StringUtils.hasText(MAVEN_HOME_ENV)
? new MavenHomeJdkDetectorSupport(new File(MAVEN_HOME_ENV))
: null;
@Override
public boolean isAvailable() {
return hasMavenHomeProperty() || hasMavenHomeEnv();
}
private boolean hasMavenHomeProperty() {
return StringUtils.hasText(maven_home_property) && mavenHome != null;
}
private boolean hasMavenHomeEnv() {
return StringUtils.hasText(MAVEN_HOME_ENV) && MAVEN_HOME != null;
}
@Override
public List<MavenInstallation> detect(JavaRuntimes.JdkInstallation jdk) {
List<MavenInstallation> installations = new ArrayList<>();
if (hasMavenHomeProperty()) {
installations.addAll(mavenHome.detect(jdk));
}
if (hasMavenHomeEnv()) {
installations.addAll(MAVEN_HOME.detect(jdk));
}
return installations;
}
}
/**
* Detector using a Maven Home directory.
*/
static class MavenHomeJdkDetectorSupport implements MavenDetector {
private final File mavenHome;
public MavenHomeJdkDetectorSupport(File mavenHome) {
this.mavenHome = mavenHome;
}
@Override
public boolean isAvailable() {
return mavenHome != null && isDirectory(mavenHome);
}
@Override
public List<MavenInstallation> detect(JavaRuntimes.JdkInstallation jdk) {
SimpleMavenRuntime mavenRuntime = new SimpleMavenRuntime(mavenHome, jdk);
return Collections.singletonList(new MavenInstallation(Version.parse(mavenRuntime.getVersion()), mavenHome));
}
}
@Value
public static class MavenInstallation implements Comparable<MavenInstallation> {
Version version;
File home;
@Override
public int compareTo(MavenInstallation o) {
return this.version.compareTo(o.version);
}
@Override
public String toString() {
return "Version " + version + " at " + home;
}
}
public static class NoSuchMavenRuntimeException extends NoSuchElementException {
private final List<MavenInstallation> installations;
private final String requiredMavenVersion;
public NoSuchMavenRuntimeException(String message, List<MavenInstallation> installations,
String requiredMavenVersion) {
super(message);
this.installations = installations;
this.requiredMavenVersion = requiredMavenVersion;
}
public List<MavenInstallation> getInstallations() {
return installations;
}
public String getRequiredMavenVersion() {
return requiredMavenVersion;
}
}
static class NoSuchMavenRuntimeExceptionFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMavenRuntimeException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchMavenRuntimeException cause) {
String action = " Make sure to install %s using your platform installation method or SDKman.%n%n"
+ " Detected Maven Runtimes are: %n" + "%s";
StringBuilder detectedRuntimes = new StringBuilder();
for (MavenInstallation installation : cause.getInstallations()) {
detectedRuntimes.append(String.format(" - %-10s %s%n", installation.getVersion(), installation.getHome()));
}
return new FailureAnalysis("⚠️ A required Maven version was not found: " + cause.getRequiredMavenVersion(),
String.format(action, cause.getRequiredMavenVersion(), detectedRuntimes), cause);
}
}
static class SimpleMavenRuntime extends MavenRuntimeSupport {
/**
* Creates a new {@link MavenRuntimeSupport}
*
* @param mavenHome
* @param jdk
*/
public SimpleMavenRuntime(File mavenHome, JavaRuntimes.JdkInstallation jdk) {
super(mavenHome, null, jdk);
}
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2023 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
*
* http://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.cli;
import lombok.Getter;
import java.io.File;
/**
* @author Mark Paluch
*/
@Getter
class InvalidMavenVersionException extends IllegalStateException {
private final String expectedVersion;
private final String actualVersion;
private final File home;
public InvalidMavenVersionException(String expectedVersion, String installedVersion, File home) {
super(String.format("Invalid Maven version: Expected %s, found version %s", expectedVersion, installedVersion));
this.expectedVersion = expectedVersion;
this.actualVersion = installedVersion;
this.home = home;
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.release.cli;
import lombok.RequiredArgsConstructor;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -25,7 +27,12 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.data.release.build.MavenProperties;
import org.springframework.data.release.build.MavenRuntime;
import org.springframework.data.release.build.MavenRuntimes;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.JavaVersion;
import org.springframework.data.release.utils.Logger;
import org.springframework.util.StringUtils;
/**
* Configuration to verify build infrastructure.
@@ -33,10 +40,14 @@ import org.springframework.data.release.utils.Logger;
* @author Mark Paluch
*/
@Configuration
@RequiredArgsConstructor
class JavaToolingConfiguration {
private static final Resource javaTools = new FileSystemResource("ci/java-tools.properties");
private final Workspace workspace;
private final Logger logger;
@Bean
PropertiesFactoryBean javaTools() {
@@ -51,8 +62,50 @@ class JavaToolingConfiguration {
}
@Bean
JavaToolingVerifier verifier(@Qualifier("javaTools") Properties javaTools, MavenRuntime mavenRuntime,
MavenProperties mavenProperties, Logger logger) {
return new JavaToolingVerifier(javaTools, mavenRuntime, mavenProperties, logger);
JavaVersions javaVersions(@Qualifier("javaTools") Properties javaTools) {
JavaVersions javaVersions = new JavaVersions(JavaVersions.parse(javaTools));
logger.log("JavaTooling", "🕵️ Checking presence of JDKs %s…",
StringUtils.collectionToDelimitedString(javaVersions.getExpectedVersions(), ", "));
for (String jdk : javaVersions.getExpectedVersions()) {
JavaVersion javaVersion = JavaVersion.of(jdk.trim());
JavaRuntimes.JdkInstallation jdkInstallation = javaVersions.getInstallation(javaVersion);
logger.log("JavaTooling", "✅ Found %s by %s", javaVersion.getName(), jdkInstallation.getImplementor());
}
return javaVersions;
}
@Bean
MavenVersion mavenVersion(@Qualifier("javaTools") Properties javaTools) {
return MavenVersion.parse(javaTools);
}
@Bean
public MavenRuntime mavenRuntime(JavaVersions javaVersions, MavenVersion mavenVersion, MavenProperties properties) {
logger.log("JavaTooling", "🕵️ Checking presence of Maven %s…", mavenVersion.getExpectedVersion());
String firstJdk = javaVersions.getExpectedVersions().get(0);
JavaRuntimes.JdkInstallation installation = javaVersions.getInstallation(firstJdk);
MavenRuntimes.Selector selector;
if (properties.getMavenHome() != null) {
selector = MavenRuntimes.Selector.builder(MavenRuntimes.detector(properties.getMavenHome()));
} else {
selector = MavenRuntimes.Selector.builder();
}
MavenRuntimes.MavenInstallation mavenInstallation = selector.version(mavenVersion.getExpectedVersion())
.getRequiredMavenInstallation(installation);
logger.log("JavaTooling", "✅ Found Maven %s", mavenInstallation);
return new MavenRuntime(workspace, logger, mavenInstallation, properties);
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2023 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
*
* http://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.cli;
import lombok.RequiredArgsConstructor;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.data.release.build.MavenProperties;
import org.springframework.data.release.build.MavenRuntime;
import org.springframework.data.release.io.JavaRuntimes.JdkInstallation;
import org.springframework.data.release.io.JavaRuntimes.Selector;
import org.springframework.data.release.model.JavaVersion;
import org.springframework.data.release.utils.Logger;
import org.springframework.util.StringUtils;
/**
* Utility to verify early on that your build environment contains all the required Java and Maven versions.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
class JavaToolingVerifier {
private final Properties javaTools;
private final MavenRuntime mavenRuntime;
private final MavenProperties mavenProperties;
private final Logger logger;
@PostConstruct
public void verify() {
String jdksProperty = javaTools.getProperty("jdks");
String[] jdks = jdksProperty.split(",");
logger.log("JavaTooling", "🕵️ Checking presence of JDKs %s…", StringUtils.arrayToDelimitedString(jdks, ", "));
for (String jdk : jdks) {
JavaVersion javaVersion = JavaVersion.of(jdk.trim());
JdkInstallation jdkInstallation = Selector.notGraalVM(javaVersion).getRequiredJdkInstallation();
logger.log("JavaTooling", "✅ Found %s by %s", javaVersion.getName(), jdkInstallation.getImplementor());
}
String expectedMavenVersion = javaTools.getProperty("maven");
logger.log("JavaTooling", "🕵️ Checking presence of Maven %s…", expectedMavenVersion);
String installedMavenVersion = mavenRuntime.getVersion();
if (!expectedMavenVersion.equals(installedMavenVersion)) {
throw new InvalidMavenVersionException(expectedMavenVersion, installedMavenVersion,
mavenProperties.getMavenHome());
}
logger.log("JavaTooling", "✅ Found Maven %s", installedMavenVersion);
}
static class InvalidMavenVersionExceptionFailureAnalyzer
extends AbstractFailureAnalyzer<InvalidMavenVersionException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, InvalidMavenVersionException cause) {
return new FailureAnalysis(
String.format("⚠️ The configured Maven version %s at %s does not match the required version %s.",
cause.getActualVersion(), cause.getHome(), cause.getExpectedVersion()),
String.format(" Make sure to use Maven %s or update your maven.maven-home property.",
cause.getExpectedVersion()),
cause);
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2024 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
*
* http://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.cli;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.model.JavaVersion;
/**
* Value object to encapsulate expected Java versions.
*
* @author Mark Paluch
*/
class JavaVersions {
private final List<String> expectedVersions;
public JavaVersions(List<String> expectedVersions) {
this.expectedVersions = expectedVersions;
}
/**
* Parse the Java versions from the given {@link Properties} at the key {@code jdks}.
*
* @param properties
* @return
*/
public static List<String> parse(Properties properties) {
String jdksProperty = properties.getProperty("jdks");
return Arrays.asList(jdksProperty.split(","));
}
/**
* Retrieve the required Java Installation for the given {@code version}.
*
* @param version
* @return
*/
public JavaRuntimes.JdkInstallation getInstallation(String version) {
return getInstallation(JavaVersion.of(version.trim()));
}
/**
* Retrieve the required Java Installation for the given {@link JavaVersion}.
*
* @param version
* @return
*/
public JavaRuntimes.JdkInstallation getInstallation(JavaVersion version) {
return JavaRuntimes.Selector.notGraalVM(version).getRequiredJdkInstallation();
}
public List<String> getExpectedVersions() {
return this.expectedVersions;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2024 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
*
* http://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.cli;
import lombok.Value;
import java.util.Properties;
import org.springframework.data.release.model.Version;
/**
* Value object for a Maven version.
*
* @author Mark Paluch
*/
@Value(staticConstructor = "of")
class MavenVersion {
String expectedVersion;
/**
* Parse the Maven version from the given {@link Properties} at the key {@code maven}.
*
* @param properties
* @return
*/
public static MavenVersion parse(Properties properties) {
return of(properties.getProperty("maven"));
}
public Version getExpectedVersion() {
return Version.parse(this.expectedVersion);
}
@Override
public String toString() {
return expectedVersion;
}
}

View File

@@ -1,2 +1,3 @@
org.springframework.boot.diagnostics.FailureAnalyzer=org.springframework.data.release.io.JavaRuntimes$NoSuchJavaRuntimeExceptionFailureAnalyzer,\
org.springframework.data.release.cli.JavaToolingVerifier.InvalidMavenVersionExceptionFailureAnalyzer
org.springframework.data.release.cli.JavaToolingVerifier.InvalidMavenVersionExceptionFailureAnalyzer,\
org.springframework.data.release.build.MavenRuntimes$NoSuchMavenRuntimeExceptionFailureAnalyzer