Add support to release Spring Data 3.0 using Java 17.

Closes #194
This commit is contained in:
Mark Paluch
2022-01-11 08:58:52 +01:00
parent e0f6caa043
commit bfc4f6701c
17 changed files with 162 additions and 329 deletions

View File

@@ -35,6 +35,7 @@ import javax.annotation.PreDestroy;
import org.apache.commons.io.IOUtils;
import org.springframework.data.release.model.JavaVersionAware;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ProjectAware;
import org.springframework.data.release.utils.ListWrapperCollector;
@@ -162,12 +163,19 @@ class BuildExecutor {
String.format("No build system plugin found for project %s!", module.getProject()));
BuildSystem buildSystem = buildSystems.getPluginFor(module.getProject(), exception);
BuildSystem buildSystemToUse;
if (module instanceof JavaVersionAware) {
buildSystemToUse = buildSystem.withJavaVersion(((JavaVersionAware) module).getJavaVersion());
} else {
buildSystemToUse = buildSystem;
}
Runnable runnable = () -> {
try {
result.complete(function.apply(buildSystem, module));
result.complete(function.apply(buildSystemToUse, module));
} catch (Exception e) {
result.completeExceptionally(e);
}

View File

@@ -87,7 +87,7 @@ public class BuildOperations {
/**
* Triggers the distribution builds for all modules participating in the given {@link Train}.
*
* @param iteration must not be {@literal null}.
* @param train must not be {@literal null}.
*/
public void distributeResources(Train train) {

View File

@@ -16,6 +16,7 @@
package org.springframework.data.release.build;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.JavaVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
@@ -77,4 +78,12 @@ interface BuildSystem extends Plugin<Project> {
* Verify general functionality and correctness of the build setup.
*/
void verify();
/**
* Prepare the build system with a Java version.
*
* @param javaVersion
* @return
*/
BuildSystem withJavaVersion(JavaVersion javaVersion);
}

View File

@@ -37,6 +37,7 @@ import org.springframework.data.release.deployment.DeploymentProperties;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Gpg;
import org.springframework.data.release.model.JavaVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
@@ -68,6 +69,12 @@ class MavenBuildSystem implements BuildSystem {
DeploymentProperties properties;
Gpg gpg;
@Override
public BuildSystem withJavaVersion(JavaVersion javaVersion) {
return new MavenBuildSystem(workspace, projectionFactory, logger, mvn.withJavaVersion(javaVersion), properties,
gpg);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#updateProjectDescriptors(org.springframework.data.release.model.ModuleIteration, org.springframework.data.release.model.TrainIteration, org.springframework.data.release.model.Phase)

View File

@@ -21,6 +21,8 @@ import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@@ -55,7 +57,7 @@ class MavenProperties {
log.info("Using {} as local Maven repository!", localRepository);
this.localRepository = new File(localRepository.replace("~", System.getProperty("user.home")));
this.localRepository = new File(localRepository.replace("~", FileUtils.getUserDirectoryPath()));
if (!this.localRepository.exists()) {
this.localRepository.mkdirs();

View File

@@ -32,8 +32,10 @@ import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.springframework.data.release.io.OsOperations;
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.Project;
import org.springframework.data.release.utils.Logger;
import org.springframework.shell.support.util.StringUtils;
@@ -48,24 +50,33 @@ import org.springframework.stereotype.Component;
class MavenRuntime {
private final Workspace workspace;
private final OsOperations os;
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.
*
* @param workspace must not be {@literal null}.
* @param os must not be {@literal null}.
* @param logger must not be {@literal null}.
* @param properties must not be {@literal null}.
*/
public MavenRuntime(Workspace workspace, OsOperations os, Logger logger, MavenProperties properties) {
@Autowired
public MavenRuntime(Workspace workspace, Logger logger, MavenProperties properties) {
this(workspace, logger, properties, JavaVersion.JAVA_8);
}
private MavenRuntime(Workspace workspace, Logger logger, MavenProperties properties,
JavaVersion requiredJavaVersion) {
this.workspace = workspace;
this.os = os;
this.logger = logger;
this.properties = properties;
this.jdk = JavaRuntimes.Selector.from(requiredJavaVersion).notGraalVM().getRequiredJdkInstallation();
}
public MavenRuntime withJavaVersion(JavaVersion javaVersion) {
return new MavenRuntime(workspace, logger, properties, javaVersion);
}
public void execute(Project project, CommandLine arguments) {
@@ -85,8 +96,10 @@ class MavenRuntime {
invoker.setLocalRepositoryDirectory(localRepository);
}
File javaHome = getJavaHome();
mavenLogger.info(String.format("Java Home: %s", jdk));
InvocationRequest request = new DefaultInvocationRequest();
request.setJavaHome(os.getJavaHome());
request.setJavaHome(javaHome);
request.setShellEnvironmentInherited(true);
request.setBaseDirectory(workspace.getProjectDirectory(project));
request.setBatchMode(true);
@@ -110,6 +123,10 @@ class MavenRuntime {
}
}
private File getJavaHome() {
return jdk.getHome().getAbsoluteFile();
}
private MavenLogger getLogger(Project project, List<CommandLine.Goal> goals) {
if (this.properties.isConsoleLogger()) {

View File

@@ -1,167 +0,0 @@
/*
* Copyright 2011-2020 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 java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.output.WriterOutputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.utils.Logger;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* Implementation of {@link OsOperations} interface.
*
* @author Stefan Schmidt
* @author Oliver Gierke
* @since 1.2.0
*/
@Component
class CommonsExecOsCommandOperations implements OsOperations {
private static final Map<String, String> ENVIRONMENT = new HashMap<>();
private final Workspace workspace;
private final Logger logger;
private final File javaHome;
@Autowired
public CommonsExecOsCommandOperations(Workspace workspace, Logger logger, IoProperties properties) throws Exception {
this.workspace = workspace;
this.logger = logger;
this.javaHome = detectJavaHome(properties);
Assert.isTrue(javaHome.exists(), String.format("Java home %s does not exist!", javaHome.getAbsolutePath()));
ENVIRONMENT.put("JAVA_HOME", javaHome.getAbsolutePath());
}
/*
* (non-Javadoc)
* @see org.springframework.shell.commands.OsOperations#executeCommand(java.lang.String)
*/
@Async
@Override
public Future<CommandResult> executeCommand(String command) throws IOException {
return executeCommand((String) null, command);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, org.springframework.data.release.model.Project)
*/
@Async
@Override
public Future<CommandResult> executeCommand(String command, Project project) throws IOException {
logger.log(project, command);
return executeCommand(command, workspace.getProjectDirectory(project), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeAndListen(org.springframework.data.release.model.Project, java.lang.String)
*/
@Override
public Future<CommandResult> executeWithOutput(String command, Project project) throws IOException {
logger.log(project, command);
return executeCommand(command, workspace.getProjectDirectory(project), false);
}
private Future<CommandResult> executeCommand(String subfolder, String command) throws IOException {
File workingDirectory = workspace.getWorkingDirectory();
File executionDirectory = subfolder == null ? workingDirectory : new File(workingDirectory, subfolder);
return executeCommand(command, executionDirectory, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeForResult(java.lang.String, org.springframework.data.release.model.Project)
*/
@Async
@Override
public String executeForResult(String command, Project project) throws Exception {
return executeCommand(command, workspace.getProjectDirectory(project), true).get().getOutput();
}
private Future<CommandResult> executeCommand(String command, File executionDirectory, boolean silent)
throws IOException {
StringWriter writer = new StringWriter();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
try (WriterOutputStream outputStream = new WriterOutputStream(writer)) {
String outerCommand = "/bin/bash -lc";
CommandLine outer = CommandLine.parse(outerCommand);
outer.addArgument(command, false);
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory(executionDirectory);
executor.setStreamHandler(new PumpStreamHandler(silent ? outputStream : System.out, null));
executor.execute(outer, ENVIRONMENT, resultHandler);
resultHandler.waitFor();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return new AsyncResult<CommandResult>(
new CommandResult(resultHandler.getExitValue(), writer.toString(), resultHandler.getException()));
}
public File getJavaHome() {
return javaHome;
}
private File detectJavaHome(IoProperties properties) throws Exception {
File javaHome = properties.getJavaHome();
if (javaHome != null) {
return javaHome;
}
String javaHomePath = executeCommand("/usr/libexec/java_home -F -v 1.8 -a x86_64 -d64").get().getOutput();
if (javaHomePath.endsWith("\n")) {
javaHomePath = javaHomePath.substring(0, javaHomePath.length() - 1);
}
return new File(javaHomePath);
}
}

View File

@@ -20,9 +20,10 @@ import lombok.extern.slf4j.Slf4j;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* @author Oliver Gierke
@@ -35,30 +36,12 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties(prefix = "io")
class IoProperties {
private File workDir, javaHome, logs;
private File workDir, logs;
public void setWorkDir(String workDir) {
log.info(String.format("🔧 Using %s as working directory!", workDir));
this.workDir = new File(workDir.replace("~", System.getProperty("user.home")));
this.workDir = new File(workDir.replace("~", FileUtils.getUserDirectoryPath()));
}
public void setJavaHome(String javaHome) {
if (!StringUtils.hasText(javaHome)) {
return;
}
File javaHomeDir = new File(javaHome.replace("~", System.getProperty("user.home")));
if (!javaHomeDir.isDirectory()) {
log.warn(String.format(
"⚠️️ Property 'io.javaHome' does not point to a valid directory ('%s')! Falling back to os.default.",
javaHomeDir.getPath()));
return;
}
log.info(String.format("🔧 Setting javaHome to: '%s'.", javaHomeDir.getPath()));
this.javaHome = javaHomeDir;
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2011-2020 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 java.io.File;
import java.io.IOException;
import java.util.concurrent.Future;
import org.springframework.data.release.model.Project;
/**
* Operations type to allow execution of native OS commands from the Spring Roo shell.
*
* @author Stefan Schmidt
* @since 1.2.0
*/
public interface OsOperations {
/**
* Attempts the execution of a commands and delegates the output to the standard logger.
*
* @param command the command to execute
* @throws IOException if an error occurs
*/
Future<CommandResult> executeCommand(String command) throws IOException;
Future<CommandResult> executeCommand(String command, Project project) throws IOException;
Future<CommandResult> executeWithOutput(String command, Project project) throws IOException;
String executeForResult(String command, Project project) throws Exception;
File getJavaHome();
}

View File

@@ -0,0 +1,38 @@
/*
* 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.model;
import lombok.Value;
import java.util.function.Predicate;
/**
* Value object representing a Java version.
*
* @author Mark Paluch
*/
@Value(staticConstructor = "of")
public class JavaVersion {
public static final JavaVersion JAVA_8 = of("Java 1.8",
version -> version.getMajor() == 1 && version.getMinor() == 8);
public static final JavaVersion JAVA_17 = of("Java 17", version -> version.getMajor() == 17);
String name;
Predicate<Version> versionDetector;
}

View File

@@ -0,0 +1,24 @@
/*
* 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.model;
/**
* @author Mark Paluch
*/
public interface JavaVersionAware {
JavaVersion getJavaVersion();
}

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.data.release.model;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.With;
import org.springframework.util.Assert;
@@ -24,24 +27,32 @@ import org.springframework.util.Assert;
* @author Mark Paluch
*/
@Value
public class Module implements VersionAware, ProjectAware, Comparable<Module> {
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Module implements VersionAware, ProjectAware, JavaVersionAware, Comparable<Module> {
private final Project project;
private final Version version;
private final Iteration customFirstIteration;
Project project;
Version version;
Iteration customFirstIteration;
@With JavaVersion javaVersion;
public Module(Project project, String version) {
this(project, version, null);
this(project, version, null, JavaVersion.JAVA_8);
}
Module(Project project, String version, String customFirstIteration) {
this(project, version, customFirstIteration, JavaVersion.JAVA_8);
}
Module(Project project, String version, String customFirstIteration, JavaVersion javaVersion) {
Assert.notNull(project, "Project must not be null!");
Assert.notNull(javaVersion, "JavaVersion must not be null!");
this.project = project;
this.version = Version.parse(version);
this.customFirstIteration = customFirstIteration == null ? null
: new Iteration(customFirstIteration, Iteration.RC1);
this.javaVersion = javaVersion;
}
public boolean hasName(String name) {
@@ -85,4 +96,9 @@ public class Module implements VersionAware, ProjectAware, Comparable<Module> {
public String toString() {
return String.format("Spring Data %s %s - %s", project.getName(), version, project.getKey());
}
public Module withJavaVersion(JavaVersion javaVersion) {
return this.javaVersion == javaVersion ? this
: new Module(this.project, this.version, this.customFirstIteration, javaVersion);
}
}

View File

@@ -25,7 +25,7 @@ import lombok.RequiredArgsConstructor;
*/
@RequiredArgsConstructor
@EqualsAndHashCode
public class ModuleIteration implements IterationVersion, ProjectAware {
public class ModuleIteration implements IterationVersion, ProjectAware, JavaVersionAware {
private final @Getter Module module;
private final @Getter TrainIteration trainIteration;
@@ -41,10 +41,16 @@ public class ModuleIteration implements IterationVersion, ProjectAware {
return module.getProject().getKey();
}
@Override
public Project getProject() {
return module.getProject();
}
@Override
public JavaVersion getJavaVersion() {
return trainIteration.getTrain().getJavaVersion();
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.model.IterationVersion#getVersion()

View File

@@ -23,4 +23,5 @@ package org.springframework.data.release.model;
public interface ProjectAware {
Project getProject();
}

View File

@@ -77,6 +77,7 @@ public class ReleaseTrains {
TURING = PASCAL.next("Turing", Transition.MAJOR, //
new Module(R2DBC, "3.0")) //
.withCalver("2022.0") //
.withJavaVersion(JavaVersion.JAVA_17)
.filterModules(module -> !module.getProject().equals(ENVERS))
.withAlwaysUseBranch(true)
.withIterations(new Train.Iterations(M1, M2, M3, M4, M5, RC1, RC2, GA, SR1, SR2, SR3, SR4, SR5));

View File

@@ -54,13 +54,14 @@ public class Train implements Streamable<Module> {
private @Nullable Version calver;
private @With Iterations iterations;
private @With boolean alwaysUseBranch;
private JavaVersion javaVersion;
public Train(String name, Module... modules) {
this(name, Arrays.asList(modules));
}
public Train(String name, Collection<Module> modules) {
this(name, Modules.of(modules), null, Iterations.DEFAULT, false);
this(name, Modules.of(modules), null, Iterations.DEFAULT, false, JavaVersion.JAVA_8);
}
/*
@@ -115,12 +116,12 @@ public class Train implements Streamable<Module> {
(it, additionalModule) -> it.hasSameProjectAs(additionalModule) ? additionalModule : it))
.collect(Collectors.toSet());
return new Train(name, Modules.of(modules), calver, iterations, alwaysUseBranch);
return new Train(name, Modules.of(modules), calver, iterations, alwaysUseBranch, javaVersion);
}
public Train filterModules(Predicate<Module> filterPredicate) {
return new Train(name, Modules.of(getModules().stream().filter(filterPredicate).collect(Collectors.toList())),
calver, iterations, alwaysUseBranch);
calver, iterations, alwaysUseBranch, javaVersion);
}
/**
@@ -158,7 +159,6 @@ public class Train implements Streamable<Module> {
return doGetTrainIteration(iterations.getIterationByName(name));
}
public ArtifactVersion getModuleVersion(Project project, Iteration iteration) {
Module module = getModule(project);
@@ -182,7 +182,16 @@ public class Train implements Streamable<Module> {
}).collect(Collectors.toSet());
return new Train(name, Modules.of(modules), calver, iterations, alwaysUseBranch);
return new Train(name, Modules.of(modules), calver, iterations, alwaysUseBranch, javaVersion);
}
public Train withJavaVersion(JavaVersion javaVersion) {
Set<Module> modules = this.modules.stream().map(it -> {
return it.withJavaVersion(javaVersion);
}).collect(Collectors.toSet());
return new Train(name, Modules.of(modules), calver, iterations, alwaysUseBranch, javaVersion);
}
/**

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2020 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 java.io.File;
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
/**
* @author Christoph Strobl
*/
public class IoPropertiesUnitTests {
@TempDir File tempDir;
IoProperties properties;
@BeforeEach
void beforeEach() {
properties = new IoProperties();
}
@Test // GH-152
void setJavaHomeToDir() {
properties.setJavaHome(tempDir.getPath());
assertThat(properties.getJavaHome()).isNotNull().isDirectory();
}
@Test // GH-152
void setJavaHomeIgnoresEmptyString() {
properties.setJavaHome("");
assertThat(properties.getJavaHome()).isNull();
}
@Test // GH-152
void setJavaHomeIgnoresFile() throws IOException {
File f = new File(tempDir, "java.txt");
f.createNewFile();
properties.setJavaHome(f.getPath());
assertThat(properties.getJavaHome()).isNull();
}
@Test // GH-152
void setJavaHomeIgnoresDirectoryThatDoesNotExist() throws IOException {
properties.setJavaHome(new File(tempDir, "java-bin").getPath());
assertThat(properties.getJavaHome()).isNull();
}
}