Skip to content

Commit

Permalink
Pass in single recipe configuration options on the command line (#816)
Browse files Browse the repository at this point in the history
* Made possible to configure options on recipes

* Polish logged message for printed data tables

* Fail to configure recipes containing other recipes

* Only parse flat options, with limited external dependencies

* Unpack CompositeRecipe containing single recipe

Due to https://github.com/openrewrite/rewrite/blob/ec09e19feba2ccff12cc20d0d684babde9aa7914/rewrite-core/src/main/java/org/openrewrite/config/Environment.java#L163

* Unit test handling of arguments

* Use a LinkedHashSet<String> for options

* Apply suggestions from code review

* Alter check for no recipe found

---------

Co-authored-by: Tim te Beek <tim@moderne.io>
Co-authored-by: Tim te Beek <timtebeek@gmail.com>
  • Loading branch information
3 people committed Jul 24, 2024
1 parent 03dea00 commit 7ecc255
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.openrewrite.*;
import org.openrewrite.config.CompositeRecipe;
import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.config.Environment;
import org.openrewrite.config.RecipeDescriptor;
import org.openrewrite.internal.InMemoryLargeSourceSet;
Expand All @@ -34,6 +36,7 @@

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
Expand All @@ -55,6 +58,10 @@ public abstract class AbstractRewriteBaseRunMojo extends AbstractRewriteMojo {
@Parameter(property = "rewrite.exportDatatables", defaultValue = "false")
protected boolean exportDatatables;

@Parameter(property = "rewrite.options")
@Nullable
protected LinkedHashSet<String> options;

/**
* Attempt to determine the root of the git repository for the given project.
* Many Gradle builds co-locate the build root with the git repository root, but that is not required.
Expand Down Expand Up @@ -91,13 +98,17 @@ protected ResultsContainer listResults(ExecutionContext ctx) throws MojoExecutio
Environment env = environment(recipeArtifactCoordinatesClassloader);

Recipe recipe = env.activateRecipes(getActiveRecipes());
if (recipe.getRecipeList().isEmpty()) {
getLog().warn("No recipes were activated. " +
"Activate a recipe with <activeRecipes><recipe>com.fully.qualified.RecipeClassName</recipe></activeRecipes> in this plugin's <configuration> in your pom.xml, " +
"or on the command line with -Drewrite.activeRecipes=com.fully.qualified.RecipeClassName");
if (recipe.getName().equals("org.openrewrite.Recipe$Noop")) {
getLog().warn("No recipes were activated." +
" Activate a recipe with <activeRecipes><recipe>com.fully.qualified.RecipeClassName</recipe></activeRecipes> in this plugin's <configuration> in your pom.xml," +
" or on the command line with -Drewrite.activeRecipes=com.fully.qualified.RecipeClassName");
return new ResultsContainer(repositoryRoot, emptyList());
}

if (options != null && !options.isEmpty()) {
configureRecipeOptions(recipe, options);
}

getLog().info("Validating active recipes...");
List<Validated<Object>> validations = new ArrayList<>();
recipe.validateAll(ctx, validations);
Expand Down Expand Up @@ -126,6 +137,72 @@ protected ResultsContainer listResults(ExecutionContext ctx) throws MojoExecutio
}
}

private static void configureRecipeOptions(Recipe recipe, Set<String> options) throws MojoExecutionException {
if (recipe instanceof CompositeRecipe ||
recipe instanceof DeclarativeRecipe ||
recipe instanceof Recipe.DelegatingRecipe ||
!recipe.getRecipeList().isEmpty()) {
// We don't (yet) support configuring potentially nested recipes, as recipes might occur more than once,
// and setting the same value twice might lead to unexpected behavior.
throw new MojoExecutionException(
"Recipes containing other recipes can not be configured from the command line: " + recipe);
}

Map<String, String> optionValues = new HashMap<>();
for (String option : options) {
String[] parts = option.split("=", 2);
if (parts.length == 2) {
optionValues.put(parts[0], parts[1]);
}
}
for (Field field : recipe.getClass().getDeclaredFields()) {
String removed = optionValues.remove(field.getName());
updateOption(recipe, field, removed);
}
if (!optionValues.isEmpty()) {
throw new MojoExecutionException(
String.format("Unknown recipe options: %s", String.join(", ", optionValues.keySet())));
}
}

private static void updateOption(Recipe recipe, Field field, @Nullable String optionValue) throws MojoExecutionException {
Object convertedOptionValue = convertOptionValue(field.getName(), optionValue, field.getType());
if (convertedOptionValue == null) {
return;
}
try {
field.setAccessible(true);
field.set(recipe, convertedOptionValue);
field.setAccessible(false);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new MojoExecutionException(
String.format("Unable to configure recipe '%s' option '%s' with value '%s'",
recipe.getClass().getSimpleName(), field.getName(), optionValue));
}
}

private static @Nullable Object convertOptionValue(String name, @Nullable String optionValue, Class<?> type)
throws MojoExecutionException {
if (optionValue == null) {
return null;
}
if (type.isAssignableFrom(String.class)) {
return optionValue;
}
if (type.isAssignableFrom(boolean.class) || type.isAssignableFrom(Boolean.class)) {
return Boolean.parseBoolean(optionValue);
}
if (type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)) {
return Integer.parseInt(optionValue);
}
if (type.isAssignableFrom(long.class) || type.isAssignableFrom(Long.class)) {
return Long.parseLong(optionValue);
}

throw new MojoExecutionException(
String.format("Unable to convert option: %s value: %s to type: %s", name, optionValue, type));
}

protected LargeSourceSet loadSourceSet(Path repositoryRoot, Environment env, ExecutionContext ctx) throws DependencyResolutionRequiredException, MojoExecutionException {
List<NamedStyles> styles = loadStyles(project, env);

Expand All @@ -144,7 +221,7 @@ protected List<Result> runRecipe(Recipe recipe, LargeSourceSet sourceSet, Execut
if (exportDatatables) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss-SSS"));
Path datatableDirectoryPath = Paths.get("target", "rewrite", "datatables", timestamp);
getLog().info(String.format("Printing Available Datatables to: %s", datatableDirectoryPath));
getLog().info(String.format("Printing available datatables to: %s", datatableDirectoryPath));
recipeRun.exportDatatablesToCsv(datatableDirectoryPath, ctx);
}

Expand Down
14 changes: 13 additions & 1 deletion src/test/java/org/openrewrite/maven/RewriteRunIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ void cloud_suitability_project(MavenExecutionResult result) {
.anySatisfy(line -> assertThat(line).contains("some.jks"));
}

@MavenTest
@SystemProperties({
@SystemProperty(value = "rewrite.activeRecipes", content = "org.openrewrite.maven.RemovePlugin"),
@SystemProperty(value = "rewrite.options", content = "groupId=org.openrewrite.maven,artifactId=rewrite-maven-plugin")
})
void command_line_options(MavenExecutionResult result) {
assertThat(result).isSuccessful().out().error().isEmpty();
assertThat(result).isSuccessful().out().warn()
.contains("Changes have been made to target/maven-it/org/openrewrite/maven/RewriteRunIT/command_line_options/project/pom.xml by:")
.contains(" org.openrewrite.maven.RemovePlugin");
assertThat(result.getMavenProjectResult().getModel().getBuild()).isNull();
}

@MavenTest
@Disabled("We should implement a simpler test to make sure that regular markers don't get added to source files")
void java_upgrade_project(MavenExecutionResult result) {
Expand All @@ -96,5 +109,4 @@ void java_compiler_plugin_project(MavenExecutionResult result) {
.filteredOn(line -> line.contains("Changes have been made"))
.hasSize(1);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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>org.openrewrite.maven</groupId>
<artifactId>command_line_options</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>RewriteRunIT#command_line_options</name>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
</plugin>
</plugins>
</build>
</project>

0 comments on commit 7ecc255

Please sign in to comment.