Add Maven launcher using Maven Invoker

This commit is contained in:
Fabian Krüger
2024-03-05 16:55:23 +01:00
committed by GitHub
parent d1ccaadd0f
commit 53fe6999bf
20 changed files with 1372 additions and 0 deletions

View File

@@ -12,6 +12,28 @@ ____
== Get started
=== Modules
[plantuml,"models",svg]
.....
component srcMaven as "src-maven" {
component srcMavenInvoker as "src-maven-invoker" {
}
component srcMavenEmbedder as "src-maven-embedder" {
}
}
component srcGradle as "src-gradle" {
component srcGradleModel as "src-gradle-model" {
}
component srcGradleParser as "src-gradle-parser" {
}
component srcGradlePlugin as "src-gradle-plugin" {
}
}
}
.....
=== Add Dependency
**Maven**

View File

@@ -17,6 +17,7 @@
<module>spring-rewrite-commons-examples</module>
<module>spring-rewrite-commons-starters</module>
<module>spring-rewrite-commons-gradle</module>
<module>spring-rewrite-commons-maven-invoker</module>
</modules>
<organization>
@@ -110,6 +111,12 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>${junit-pioneer.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.rewrite</groupId>
<artifactId>spring-rewrite-commons</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-rewrite-commons-maven-invoker</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-shared-utils</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>${maven-invoker.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-util</artifactId>
<version>${maven-resolver.version}</version> <!-- Please check for the latest version available -->
</dependency>
<dependency>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>${rewrite-maven-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
<version>${maven-resolver.version}</version> <!-- Check for the latest version -->
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-impl</artifactId>
<version>${maven-resolver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-wagon</artifactId>
<version>${maven-resolver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<version>${maven-resolver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-http</artifactId>
<version>${maven-wagon-http.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-compat</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
/**
* @author Fabian Krüger
*/
public class BuildConfig {
private boolean skipTests = false;
private MemorySettings memorySettings;
BuildConfig(boolean skipTests, MemorySettings memorySettings) {
this.skipTests = skipTests;
this.memorySettings = memorySettings;
}
private BuildConfig(boolean skipTests) {
this.skipTests = skipTests;
}
public static BuildConfig skipTests() {
BuildConfig buildConfig = new BuildConfig(true);
return buildConfig;
}
public static BuildConfig defaultConfig() {
return new BuildConfig(false);
}
public static Builder builder() {
return new Builder();
}
boolean isSkipTests() {
return skipTests;
}
public MemorySettings getMemorySettings() {
return memorySettings;
}
public static class Builder {
private boolean skipTests;
private MemorySettings memorySettings = MemorySettings.of("31M", "256M");
public Builder skipTests(boolean b) {
this.skipTests = b;
return this;
}
public BuildConfig build() {
return new BuildConfig(skipTests, memorySettings);
}
public Builder withMemory(String min, String max) {
this.memorySettings = MemorySettings.of(min, max);
return this;
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
/**
* @author Fabian Krüger
*/
public class DebugConfig {
private int port = 5005;
private boolean suspend = true;
private boolean isDebugEnabled = true;
public static DebugConfig from(int port, boolean suspend) {
return new DebugConfig(port, suspend, true);
}
public static DebugConfig fromDefault() {
return new DebugConfig(5005, false, true);
}
public static DebugConfig disabled() {
return new DebugConfig(5005, false, false);
}
private DebugConfig(int port, boolean suspend, boolean isDebugEnabled) {
this.port = port;
this.suspend = suspend;
this.isDebugEnabled = isDebugEnabled;
}
public int getPort() {
return port;
}
public char isSuspend() {
return suspend ? 'y' : 'n';
}
public boolean isDebugEnabled() {
return isDebugEnabled;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
import org.apache.maven.shared.invoker.*;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* @author Fabian Krüger
*/
public class MavenInvocationRequestFactory {
@NotNull
public InvocationRequest createMavenInvocationRequest(Path baseDir, DebugConfig debugConfig,
BuildConfig buildConfig, List<String> givenGoals, Consumer<String> lineConsumer) {
List<String> goals = new ArrayList<>(givenGoals);
String mavenHome = System.getenv("MAVEN_HOME");
if (mavenHome == null) {
mavenHome = System.getenv("M2_HOME");
if (mavenHome == null) {
throw new IllegalStateException(
"MAVEN_HOME or M2_HOME must be set but System.getenv(\"MAVEN_HOME\") and System.getenv(\"M2_HOME\") returned null.");
}
}
if (buildConfig.isSkipTests()) {
goals.add("-DskipTests");
}
InvocationRequest request = new DefaultInvocationRequest();
request.setGoals(goals);
request.setBatchMode(true);
request.setMavenHome(new File(mavenHome));
request.setBaseDirectory(baseDir.toFile());
request.setOutputHandler(s -> lineConsumer.accept(s));
StringBuilder mavenOpts = new StringBuilder();
if (buildConfig.getMemorySettings() != null) {
mavenOpts.append("-Xms%s -Xmx%s ".formatted(buildConfig.getMemorySettings().getMin(),
buildConfig.getMemorySettings().getMax()));
}
if (debugConfig != null && debugConfig.isDebugEnabled()) {
mavenOpts.append(" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=%s,address=%s "
.formatted(debugConfig.isSuspend(), debugConfig.getPort()));
}
request.setMavenOpts(mavenOpts.toString());
return request;
}
}

View File

@@ -0,0 +1,379 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
import org.apache.maven.shared.invoker.*;
import javax.swing.text.html.Option;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Executes OpenRewrite recipes by invoking local Maven installation. It requires
* MAVEN_HOME or M2_HOME to be set!
*
* @author Fabian Krüger
*/
public class MavenInvocationRewriteRecipeLauncher {
private MavenInvocationRequestFactory mavenInvocationRequestFactory = new MavenInvocationRequestFactory();
// private Invoker invoker = new DefaultInvoker();
public static Builders.OnDirBuilder applyRecipes(String... recipeNames) {
MavenInvokerRewriteRecipeLauncherBuilder builders = new MavenInvokerRewriteRecipeLauncherBuilder();
return builders.applyRecipes(recipeNames);
}
public static Builders.OnDirBuilder applyRecipes(List<String> recipeNames) {
MavenInvokerRewriteRecipeLauncherBuilder builders = new MavenInvokerRewriteRecipeLauncherBuilder();
return builders.applyRecipes(recipeNames);
}
private InvocationResult invokeRewritePlugin(Invoker invoker, Path baseDir, List<String> recipeNames,
List<String> dependencies, DebugConfig debugConfig, BuildConfig buildConfig, Consumer<String> lineConsumer,
RewritePluginGoals rewritePluginGoals) {
validateBaseDir(baseDir);
if (recipeNames.isEmpty()) {
throw new IllegalArgumentException("No recipe names provided.");
}
String openRewriteCommand = renderOpenRewriteCommand(recipeNames, dependencies, rewritePluginGoals);
return invokeMaven(invoker, baseDir, debugConfig, buildConfig, lineConsumer, openRewriteCommand);
}
public InvocationResult invokeRewritePlugin(Path baseDir, List<String> recipeNames, List<String> dependencies,
DebugConfig debugConfig, BuildConfig buildConfig, Consumer<String> lineConsumer,
RewritePluginGoals rewritePluginGoals) {
validateBaseDir(baseDir);
if (recipeNames.isEmpty()) {
throw new IllegalArgumentException("No recipe names provided.");
}
String openRewriteCommand = renderOpenRewriteCommand(recipeNames, dependencies, rewritePluginGoals);
return invokeMaven(new DefaultInvoker(), baseDir, debugConfig, buildConfig, lineConsumer, openRewriteCommand);
}
private void validateBaseDir(Path baseDir) {
if (baseDir == null) {
throw new IllegalArgumentException("Given baseDir was null.");
}
else if (!Files.exists(baseDir)) {
throw new IllegalArgumentException("Given baseDir '%s' does not exist.".formatted(baseDir));
}
}
private InvocationResult invokeMaven(Invoker invoker, Path baseDir, DebugConfig debugConfig,
BuildConfig buildConfig, Consumer<String> lineConsumer, String openRewriteCommand) {
List<String> goals = new ArrayList<>();
goals.add("clean");
goals.add("package");
goals.add("--fail-at-end");
goals.add(openRewriteCommand);
InvocationRequest request = mavenInvocationRequestFactory.createMavenInvocationRequest(baseDir, debugConfig,
buildConfig, goals, lineConsumer);
try {
return invoker.execute(request);
}
catch (MavenInvocationException e) {
throw new RuntimeException(e);
}
}
// public static InvocationResult invokeMaven(Path baseDir, BuildConfig buildConfig,
// DebugConfig debugConfig,
// Consumer<String> lineConsumer, List<String> providedGoals) {
// List<String> goals = new ArrayList<>(providedGoals);
//
// InvocationRequest mavenInvocationRequest = new
// MavenInvocationRequestFactory().createMavenInvocationRequest(baseDir, debugConfig,
// buildConfig, providedGoals, lineConsumer);
//
// String mavenHome = System.getenv("MAVEN_HOME");
// if (mavenHome == null) {
// mavenHome = System.getenv("M2_HOME");
// if (mavenHome == null) {
// throw new IllegalStateException(
// "MAVEN_HOME or M2_HOME must be set but System.getenv(\"MAVEN_HOME\") and
// System.getenv(\"M2_HOME\") returned null.");
// }
// }
//
// if (buildConfig.isSkipTests()) {
// goals.add("-DskipTests");
// }
//
// Invoker invoker = new DefaultInvoker();
// InvocationRequest request = new DefaultInvocationRequest();
// request.setGoals(goals);
// request.setBatchMode(true);
// request.setMavenHome(new File(mavenHome));
// request.setBaseDirectory(baseDir.toFile());
// request.setOutputHandler(s -> lineConsumer.accept(s));
//
// StringBuilder mavenOpts = new StringBuilder();
// mavenOpts.append("-Xms1G -Xmx6G ");
// if (debugConfig != null && debugConfig.isDebugEnabled()) {
// mavenOpts.append(" -Xdebug
// -Xrunjdwp:transport=dt_socket,server=y,suspend=%s,address=%s "
// .formatted(debugConfig.isSuspend(), debugConfig.getPort()));
// }
// request.setMavenOpts(mavenOpts.toString());
//
// try {
// InvocationResult result = invoker.execute(request);
// return result;
// }
// catch (MavenInvocationException e) {
// throw new RuntimeException(e);
// }
// }
private static String renderOpenRewriteCommand(List<String> recipeNames, List<String> dependencies,
RewritePluginGoals rewritePluginGoal) {
StringBuilder sb = new StringBuilder();
sb.append("org.openrewrite.maven:rewrite-maven-plugin:").append(rewritePluginGoal.getGoalName()).append(" ");
String recipesList = recipeNames.stream().collect(Collectors.joining(","));
sb.append("-Drewrite.activeRecipes=").append(recipesList);
if (!dependencies.isEmpty()) {
String dependenciesList = dependencies.stream().collect(Collectors.joining(","));
sb.append(" ").append("-Drewrite.recipeArtifactCoordinates=").append(dependenciesList);
}
return sb.toString();
}
public class Builders {
public interface StartBuilder {
OnDirBuilder applyRecipes(String... recipeNames);
OnDirBuilder applyRecipes(List<String> recipeNames);
}
public interface OnDirBuilder {
OptionalBuilder onDir(Path baseDir);
}
public interface OptionalBuilder {
OptionalBuilder withDebugConfig(DebugConfig debugConfig);
OptionalBuilder withDependencies(String... dependencyGavs);
OptionalBuilder withDependencies(List<String> dependencyGavs);
OptionalBuilder withBuildConfig(BuildConfig buildConfig);
OptionalBuilder withOutputListener(Consumer<String> outputListener);
OptionalBuilder withInvoker(Invoker invoker);
InvocationResult run();
InvocationResult runNoFork();
InvocationResult dryRun();
InvocationResult dryRunNoFork();
InvocationResult discover();
InvocationResult cyclonedx();
}
}
public static class MavenInvokerRewriteRecipeLauncherBuilder
implements Builders.StartBuilder, Builders.OnDirBuilder, Builders.OptionalBuilder {
private Path baseDir;
private List<String> recipes = new ArrayList<>();
private List<String> dependencies = new ArrayList<>();
private BuildConfig buildConfig = BuildConfig.defaultConfig();
private DebugConfig debugConfig = DebugConfig.disabled();
private Consumer<String> outputListener = line -> System.out.println(line);
private Invoker invoker = new DefaultInvoker();
public Builders.OptionalBuilder onDir(String baseDir) {
this.baseDir = Path.of(baseDir).toAbsolutePath().normalize();
return this;
}
@Override
public Builders.OnDirBuilder applyRecipes(String... recipeNames) {
validateRecipeNames(Arrays.asList(recipeNames));
return this.applyRecipes(Arrays.asList(recipeNames));
}
@Override
public Builders.OnDirBuilder applyRecipes(List<String> recipeNames) {
validateRecipeNames(recipeNames);
this.recipes = recipeNames;
return this;
}
@Override
public Builders.OptionalBuilder onDir(Path baseDir) {
this.baseDir = baseDir.toAbsolutePath().normalize();
return this;
}
@Override
public Builders.OptionalBuilder withOutputListener(Consumer<String> outputListener) {
this.outputListener = outputListener;
return this;
}
@Override
public Builders.OptionalBuilder withInvoker(Invoker invoker) {
this.invoker = invoker;
return this;
}
/**
* mvn rewrite:run - RunBuilder the configured recipes and apply the changes
* locally.
*/
@Override
public InvocationResult run() {
return executeRewriteGoal(invoker, baseDir, recipes, dependencies, debugConfig, buildConfig, outputListener,
RewritePluginGoals.RUN);
}
/**
* mvn rewrite:runNoFork - RunBuilder the configured recipes and apply the changes
* locally. This variant does not fork the Maven life cycle and can be a more
* efficient choice when using Rewrite within a CI workflow when combined with
* other Maven goals.
*/
@Override
public InvocationResult runNoFork() {
return executeRewriteGoal(invoker, baseDir, recipes, dependencies, debugConfig, buildConfig, outputListener,
RewritePluginGoals.RUN_NO_FORK);
}
/**
* mvn rewrite:dryRun - Generate warnings to the console for any recipe that would
* make changes and generates a diff file in each maven modules' target folder.
*/
@Override
public InvocationResult dryRun() {
return executeRewriteGoal(invoker, baseDir, recipes, dependencies, debugConfig, buildConfig, outputListener,
RewritePluginGoals.DRY_RUN);
}
/**
* mvn rewrite:dryRunNoFork - Generate warnings to the console for any recipe that
* would make changes and generates a diff file in each maven modules' target
* folder. This variant does not fork the Maven life cycle and can be a more
* efficient choice when using Rewrite within a CI workflow when combined with
* other Maven goals.
*/
@Override
public InvocationResult dryRunNoFork() {
return executeRewriteGoal(invoker, baseDir, recipes, dependencies, debugConfig, buildConfig, outputListener,
RewritePluginGoals.DRY_RUN_NO_FORK);
}
/**
* mvn rewrite:discover - Generate a report of available recipes found on the
* classpath.
*/
@Override
public InvocationResult discover() {
return executeRewriteGoal(invoker, baseDir, recipes, dependencies, debugConfig, buildConfig, outputListener,
RewritePluginGoals.DISCOVER);
}
/**
* mvn rewrite:cyclonedx - Generate a CycloneDx bill of materials outlining the
* project's dependencies, including transitive dependencies.
*
* @see <a href="https://cyclonedx.org/">CycloneDx</a>
*/
@Override
public InvocationResult cyclonedx() {
return executeRewriteGoal(invoker, baseDir, recipes, dependencies, debugConfig, buildConfig, outputListener,
RewritePluginGoals.CYCLONEDX);
}
private InvocationResult executeRewriteGoal(Invoker invoker, Path baseDir, List<String> recipeNames,
List<String> dependencies, DebugConfig debugConfig, BuildConfig buildConfig,
Consumer<String> lineConsumer, RewritePluginGoals rewritePluginGoals) {
MavenInvocationRewriteRecipeLauncher launcher = new MavenInvocationRewriteRecipeLauncher();
InvocationResult result = launcher.invokeRewritePlugin(invoker, baseDir, recipeNames, dependencies,
debugConfig, buildConfig, lineConsumer, rewritePluginGoals);
return result;
}
private void validateRecipeNames(List<String> recipeNames) {
if (recipeNames.isEmpty()) {
throw new IllegalArgumentException("At least one recipe name must be provided.");
}
if (recipeNames.stream().anyMatch(String::isBlank)) {
throw new IllegalArgumentException("At least one recipe name was blank.");
}
}
@Override
public Builders.OptionalBuilder withDependencies(String... dependencyGavs) {
return withDependencies(Arrays.asList(dependencyGavs));
}
@Override
public Builders.OptionalBuilder withDependencies(List<String> dependencyGavs) {
this.dependencies = dependencyGavs;
return this;
}
@Override
public Builders.OptionalBuilder withBuildConfig(BuildConfig buildConfig) {
this.buildConfig = buildConfig;
return this;
}
@Override
public Builders.OptionalBuilder withDebugConfig(DebugConfig debugConfig) {
this.debugConfig = debugConfig;
return this;
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
import org.apache.maven.shared.invoker.*;
/**
* @author Fabian Krüger
*/
public class MavenInvoker {
private Invoker invoker = new DefaultInvoker();
public InvocationResult invoke(InvocationRequest request) {
try {
return invoker.execute(request);
}
catch (MavenInvocationException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
/**
* @author Fabian Krüger
*/
public class MemorySettings {
private final String min;
private final String max;
public MemorySettings(String min, String max) {
this.min = min;
this.max = max;
}
public static MemorySettings of(String min, String max) {
return new MemorySettings(min, max);
}
public String getMin() {
return min;
}
public String getMax() {
return max;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
/**
* @author Fabian Krüger
*/
public enum RewritePluginGoals {
RUN("run"), RUN_NO_FORK("runNoFork"), DRY_RUN("dryRun"), DRY_RUN_NO_FORK("dryRunNoFork"), DISCOVER("discover"),
CYCLONEDX("cyclonedx");
private final String goalName;
RewritePluginGoals(String goalName) {
this.goalName = goalName;
}
public String getGoalName() {
return goalName;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.utils.cli.CommandLineException;
public class DummyInvocationResult implements InvocationResult {
@Override
public CommandLineException getExecutionException() {
return null;
}
@Override
public int getExitCode() {
return 0;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2021 - 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
*
* 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.rewrite.maven;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Fabian Krüger
*/
public class MavenInvocationRequestFactoryTest {
@Test
@DisplayName("build standard request")
void buildStandardRequest() {
MavenInvocationRequestFactory sut = new MavenInvocationRequestFactory();
List<String> goals = List.of("clean", "package", "--fail-at-end");
Consumer<String> lineConsumer = s -> {
};
Path baseDir = Path.of("./testcode/does-not-exist");
InvocationRequest request = sut.createMavenInvocationRequest(baseDir, DebugConfig.disabled(),
BuildConfig.defaultConfig(), goals, lineConsumer);
assertThat(request.getGoals()).isEqualTo(goals);
assertThat(request.getBaseDirectory()).isEqualTo(baseDir.toFile());
assertThat(request.getMavenOpts()).isBlank();
assertThat(request.getOutputHandler(null)).isNotNull();
}
@Test
@DisplayName("build debug request")
void buildDebugRequest() {
MavenInvocationRequestFactory sut = new MavenInvocationRequestFactory();
List<String> goals = List.of("clean", "package", "--fail-at-end");
Consumer<String> lineConsumer = s -> {
};
Path baseDir = Path.of("./testcode/does-not-exist");
InvocationRequest request = sut.createMavenInvocationRequest(baseDir, DebugConfig.from(1234, true),
BuildConfig.defaultConfig(), goals, lineConsumer);
assertThat(request.getMavenOpts())
.isEqualTo(" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1234 ");
}
@Test
@DisplayName("throws exception on empty baseDir")
void errorListener() {
MavenInvocationRequestFactory sut = new MavenInvocationRequestFactory();
List<String> lines = new ArrayList<>();
Path baseDir = Path.of("./testcode/does-not-exist").toAbsolutePath().normalize();
@NotNull
InvocationRequest result = sut.createMavenInvocationRequest(baseDir, DebugConfig.disabled(),
BuildConfig.defaultConfig(), List.of("clean"), lines::add);
}
@Test
@DisplayName("build declared memory request and skip tests")
void buildDeclaredMemoryRequestAndSkipTests() {
MavenInvocationRequestFactory sut = new MavenInvocationRequestFactory();
List<String> goals = List.of("clean", "package", "--fail-at-end");
Consumer<String> lineConsumer = s -> {
};
Path baseDir = Path.of("./testcode/does-not-exist");
InvocationRequest request = sut.createMavenInvocationRequest(baseDir, DebugConfig.disabled(),
BuildConfig.builder().withMemory("12M", "2G").skipTests(true).build(), goals, lineConsumer);
assertThat(request.getMavenOpts()).isEqualTo("-Xms12M -Xmx2G ");
assertThat(request.getGoals()).contains("-DskipTests");
}
}

View File

@@ -0,0 +1,156 @@
package org.springframework.rewrite.maven;
/*
* Copyright 2021 - 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
*
* 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.
*/
import org.apache.maven.shared.invoker.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
/**
* @author Fabian Krüger
*/
class MavenInvocationRewriteRecipeLauncherIntegrationTest {
@Mock
private Path baseDir = Path.of("./testcode/maven-projects/simple").toAbsolutePath().normalize();
@Test
@DisplayName("invokeRewritePlugin() method test")
void invokeRewritePluginMethodTest() {
List<String> capturedLines = new ArrayList<>();
MavenInvocationRewriteRecipeLauncher sut = new MavenInvocationRewriteRecipeLauncher();
sut.invokeRewritePlugin(Path.of("./testcode/maven-projects/simple"),
List.of("org.openrewrite.java.RemoveUnusedImports"), new ArrayList<>(), DebugConfig.from(9090, false),
BuildConfig.builder().skipTests(true).withMemory("64M", "256M").build(), capturedLines::add,
RewritePluginGoals.DRY_RUN);
assertThat(capturedLines).contains("Listening for transport dt_socket at address: 9090",
"[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ simple ---", "[INFO] Tests are skipped.",
"[INFO] Using active recipe(s) [org.openrewrite.java.RemoveUnusedImports]",
"[INFO] Using active styles(s) []", "[INFO] BUILD SUCCESS");
}
@Test
@DisplayName("mavenInvocationRequestBuilder success")
void mavenInvoker() {
List<String> capturedLines = new ArrayList<>();
Path baseDir = Path.of("./testcode/maven-projects/simple").toAbsolutePath().normalize();
InvocationResult result = MavenInvocationRewriteRecipeLauncher
.applyRecipes("org.openrewrite.java.RemoveUnusedImports", "org.openrewrite.java.format.AutoFormat")
.onDir(baseDir)
.withOutputListener(capturedLines::add)
.dryRun();
assertThat(result.getExitCode()).isEqualTo(0);
assertThat(capturedLines).contains("[INFO] Scanning for projects...",
"[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ simple ---",
"[INFO] Using active recipe(s) [org.openrewrite.java.RemoveUnusedImports, org.openrewrite.java.format.AutoFormat]",
"[INFO] BUILD SUCCESS");
}
@Test
@DisplayName("with debug enabled")
void withDebugEnabled() {
List<String> capturedLines = new ArrayList<>();
InvocationResult result = MavenInvocationRewriteRecipeLauncher
.applyRecipes("org.openrewrite.java.RemoveUnusedImports")
.onDir(Path.of("./testcode/maven-projects/simple").toAbsolutePath().normalize())
.withOutputListener(capturedLines::add)
.withDebugConfig(DebugConfig.fromDefault())
.withBuildConfig(BuildConfig.builder().withMemory("1G", "6G").build())
.run();
assertThat(capturedLines).contains("Listening for transport dt_socket at address: 5005");
assertThat(result.getExitCode()).isEqualTo(0);
}
@Test
@DisplayName("cyclonedx")
void cyclonedx() {
List<String> lines = new ArrayList<>();
MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder builder = getBuilder(lines);
InvocationResult result = builder.cyclonedx();
assertThat(result.getExitCode()).isEqualTo(0);
assertThat(lines.stream().anyMatch(l -> l.endsWith(":cyclonedx (default-cli) @ simple ---"))).isTrue();
assertThat(baseDir.resolve("target/simple-0.1.0-SNAPSHOT-cyclonedx.xml")).exists();
}
@Test
@DisplayName("dryRun")
void dryRun() {
List<String> lines = new ArrayList<>();
MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder builder = getBuilder(lines);
InvocationResult result = builder.dryRun();
assertThat(result.getExitCode()).isEqualTo(0);
assertThat(lines.stream().anyMatch(l -> l.endsWith(":dryRun (default-cli) @ simple ---"))).isTrue();
}
@Test
@DisplayName("dryRunNoFork")
void dryRunNoFork() {
List<String> lines = new ArrayList<>();
MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder builder = getBuilder(lines);
InvocationResult result = builder.dryRunNoFork();
assertThat(result.getExitCode()).isEqualTo(0);
assertThat(lines.stream().anyMatch(l -> l.endsWith(":dryRunNoFork (default-cli) @ simple ---"))).isTrue();
}
@Test
@DisplayName("runNoFork")
void runNoFork() {
List<String> lines = new ArrayList<>();
MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder builder = getBuilder(lines);
InvocationResult result = builder.dryRunNoFork();
assertThat(result.getExitCode()).isEqualTo(0);
assertThat(lines.stream().anyMatch(l -> l.endsWith(":dryRunNoFork (default-cli) @ simple ---"))).isTrue();
}
@Test
@DisplayName("discover")
void discover() {
List<String> lines = new ArrayList<>();
MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder builder = getBuilder(lines);
InvocationResult result = builder.discover();
assertThat(result.getExitCode()).isEqualTo(0);
assertThat(lines).contains("[INFO] Available Recipes:", "[INFO] org.openrewrite.FindSourceFiles");
assertThat(lines.stream().anyMatch(l -> l.endsWith(":discover (default-cli) @ simple ---"))).isTrue();
}
private static MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder getBuilder(List<String> lines) {
Path baseDir = Path.of("./testcode/maven-projects/simple").toAbsolutePath().normalize();
MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder builder = MavenInvocationRewriteRecipeLauncher
.applyRecipes("org.openrewrite.java.RemoveUnusedImports")
.onDir(baseDir)
.withOutputListener(lines::add);
return builder;
}
}

View File

@@ -0,0 +1,192 @@
package org.springframework.rewrite.maven;
/*
* Copyright 2021 - 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
*
* 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.
*/
import org.apache.maven.shared.invoker.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.springframework.test.util.ReflectionTestUtils;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
/**
* @author Fabian Krüger
*/
class MavenInvocationRewriteRecipeLauncherTest {
private static final int SUCCESS = 0;
private MavenInvocationRewriteRecipeLauncher.Builders.OptionalBuilder invokerBuilder;
@Mock
Invoker mavenInvoker = mock(Invoker.class);
private Path baseDir = Path.of("./testcode/maven-projects/simple").toAbsolutePath().normalize();
private List<String> capturedLines = new ArrayList<>();
private Consumer<String> outputListener = capturedLines::add;
@BeforeEach
void beforeEach() throws MavenInvocationException {
outputListener = capturedLines::add;
invokerBuilder = MavenInvocationRewriteRecipeLauncher
.applyRecipes("org.openrewrite.java.RemoveUnusedImports", "org.openrewrite.java.format.AutoFormat")
.onDir(baseDir)
.withInvoker(mavenInvoker)
.withOutputListener(outputListener);
InvocationResult result = new DummyInvocationResult();
when(mavenInvoker.execute(any(InvocationRequest.class))).thenReturn(result);
}
@Test
@DisplayName("minimal")
void minimal() throws MavenInvocationException {
InvocationResult actualResult = invokerBuilder.run();
assertThat(actualResult.getExitCode()).isEqualTo(0);
String[] expectedGoals = { "clean", "package", "--fail-at-end",
"org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.RemoveUnusedImports,org.openrewrite.java.format.AutoFormat" };
verifyCallToMaven(expectedGoals);
}
@Test
@DisplayName("with dependencies")
void withDependencies() throws MavenInvocationException {
InvocationResult actualResult = invokerBuilder
.withDependencies("com.example:some-dep:1.0.0", "com.example:another-dep:1.0.0")
.run();
assertThat(actualResult.getExitCode()).isEqualTo(0);
String[] expectedGoals = { "clean", "package", "--fail-at-end",
"org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.RemoveUnusedImports,org.openrewrite.java.format.AutoFormat -Drewrite.recipeArtifactCoordinates=com.example:some-dep:1.0.0,com.example:another-dep:1.0.0" };
verifyCallToMaven(expectedGoals);
}
@Test
@DisplayName("with build tools config")
void withBuildToolConfig() throws MavenInvocationException {
InvocationResult actualResult = invokerBuilder
.withBuildConfig(BuildConfig.builder().withMemory("64M", "256M").skipTests(true).build())
.run();
assertThat(actualResult.getExitCode()).isEqualTo(0);
String[] expectedGoals = { "clean", "package", "--fail-at-end",
"org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.RemoveUnusedImports,org.openrewrite.java.format.AutoFormat" };
verifyCallToMaven(expectedGoals, BuildConfig.builder().withMemory("64M", "256M").skipTests(true).build());
}
@Test
@DisplayName("builder")
void builder(@TempDir Path tempDir) {
InvocationResult result = MavenInvocationRewriteRecipeLauncher.applyRecipes("recipe1").onDir(tempDir).run();
MavenInvocationRewriteRecipeLauncher.applyRecipes("recipe1").onDir(tempDir).withDependencies("", "").run();
MavenInvocationRewriteRecipeLauncher.applyRecipes(List.of("NoName")).onDir(tempDir).withDependencies("").run();
// build tool config only
MavenInvocationRewriteRecipeLauncher.applyRecipes(List.of("NoName"))
.onDir(tempDir)
.withDependencies("")
.withBuildConfig(BuildConfig.defaultConfig())
.run();
// debug config only
MavenInvocationRewriteRecipeLauncher.applyRecipes("NoName")
.onDir(tempDir)
.withDependencies("")
.withDebugConfig(DebugConfig.disabled())
.run();
MavenInvocationRewriteRecipeLauncher.applyRecipes(List.of("NoName"))
.onDir(tempDir)
.withDependencies("")
.withDebugConfig(DebugConfig.disabled())
.dryRun();
// build tool and debug config
MavenInvocationRewriteRecipeLauncher.applyRecipes(List.of("NoName"))
.onDir(tempDir)
.withDependencies("")
.withBuildConfig(BuildConfig.defaultConfig())
.withDebugConfig(DebugConfig.disabled())
.run();
MavenInvocationRewriteRecipeLauncher.applyRecipes("NoName").onDir(tempDir).run();
}
@Test
@DisplayName("missing recipeNames should throw exception")
void missingRecipeNamesShouldThrowException() {
assertThrows(IllegalArgumentException.class, () -> {
MavenInvocationRewriteRecipeLauncher.applyRecipes("").onDir(Path.of("")).run();
});
}
@Test
@DisplayName("mavenInvocationRequestBuilder extra memory")
void mavenInvokerExtraMemory() {
List<String> lines = new ArrayList<>();
Path baseDir = Path.of("./testcode/maven-projects/simple").toAbsolutePath().normalize();
InvocationResult result = MavenInvocationRewriteRecipeLauncher
.applyRecipes("org.openrewrite.java.RemoveUnusedImports")
.onDir(baseDir)
.withBuildConfig(BuildConfig.builder().skipTests(false).withMemory("32M", "1G").build())
.withOutputListener(lines::add)
.dryRun();
assertThat(result.getExitCode()).isEqualTo(SUCCESS);
assertThat(lines).contains("[INFO] Scanning for projects...",
"[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ simple ---", "[INFO] BUILD SUCCESS");
}
private void verifyCallToMaven(String[] expectedGoals) throws MavenInvocationException {
BuildConfig buildConfig = BuildConfig.defaultConfig();
verifyCallToMaven(expectedGoals, buildConfig, DebugConfig.disabled(), baseDir, outputListener,
Arrays.asList(expectedGoals));
}
private void verifyCallToMaven(String[] expectedGoals, BuildConfig buildConfig) throws MavenInvocationException {
verifyCallToMaven(expectedGoals, buildConfig, DebugConfig.disabled(), baseDir, outputListener,
Arrays.asList(expectedGoals));
}
private void verifyCallToMaven(String[] expectedGoals, BuildConfig buildConfig, DebugConfig debugConfig,
Path baseDir, Consumer<String> lineConsumer, List<String> givenGoals) throws MavenInvocationException {
InvocationRequest mavenInvocationRequest = new MavenInvocationRequestFactory()
.createMavenInvocationRequest(baseDir, debugConfig, buildConfig, givenGoals, lineConsumer);
ArgumentCaptor<InvocationRequest> captor = ArgumentCaptor.forClass(InvocationRequest.class);
verify(mavenInvoker).execute(captor.capture());
InvocationRequest request = captor.getValue();
assertThat(request).usingRecursiveComparison().isEqualTo(mavenInvocationRequest);
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.acme</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>application</artifactId>
<dependencies>
<dependency>
<groupId>com.acme</groupId>
<artifactId>component</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.acme</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>component</artifactId>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.acme</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>application</module>
<module>component</module>
</modules>
</project>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.maven</groupId>
<artifactId>simple</artifactId>
<version>0.1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>