From 7ecc255a64779b044f6f3821987fe08fba0a30db Mon Sep 17 00:00:00 2001 From: Marvin Date: Wed, 24 Jul 2024 12:47:44 +0000 Subject: [PATCH] Pass in single recipe configuration options on the command line (#816) * 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 for options * Apply suggestions from code review * Alter check for no recipe found --------- Co-authored-by: Tim te Beek Co-authored-by: Tim te Beek --- .../maven/AbstractRewriteBaseRunMojo.java | 87 +++++++++++++++++-- .../org/openrewrite/maven/RewriteRunIT.java | 14 ++- .../RewriteRunIT/command_line_options/pom.xml | 27 ++++++ 3 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 src/test/resources-its/org/openrewrite/maven/RewriteRunIT/command_line_options/pom.xml diff --git a/src/main/java/org/openrewrite/maven/AbstractRewriteBaseRunMojo.java b/src/main/java/org/openrewrite/maven/AbstractRewriteBaseRunMojo.java index d96d8557..35030088 100644 --- a/src/main/java/org/openrewrite/maven/AbstractRewriteBaseRunMojo.java +++ b/src/main/java/org/openrewrite/maven/AbstractRewriteBaseRunMojo.java @@ -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; @@ -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; @@ -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 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. @@ -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 com.fully.qualified.RecipeClassName in this plugin's 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 com.fully.qualified.RecipeClassName in this plugin's 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> validations = new ArrayList<>(); recipe.validateAll(ctx, validations); @@ -126,6 +137,72 @@ protected ResultsContainer listResults(ExecutionContext ctx) throws MojoExecutio } } + private static void configureRecipeOptions(Recipe recipe, Set 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 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 styles = loadStyles(project, env); @@ -144,7 +221,7 @@ protected List 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); } diff --git a/src/test/java/org/openrewrite/maven/RewriteRunIT.java b/src/test/java/org/openrewrite/maven/RewriteRunIT.java index 2c94b03a..8d8ff285 100644 --- a/src/test/java/org/openrewrite/maven/RewriteRunIT.java +++ b/src/test/java/org/openrewrite/maven/RewriteRunIT.java @@ -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) { @@ -96,5 +109,4 @@ void java_compiler_plugin_project(MavenExecutionResult result) { .filteredOn(line -> line.contains("Changes have been made")) .hasSize(1); } - } diff --git a/src/test/resources-its/org/openrewrite/maven/RewriteRunIT/command_line_options/pom.xml b/src/test/resources-its/org/openrewrite/maven/RewriteRunIT/command_line_options/pom.xml new file mode 100644 index 00000000..9c6118a6 --- /dev/null +++ b/src/test/resources-its/org/openrewrite/maven/RewriteRunIT/command_line_options/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + org.openrewrite.maven + command_line_options + 1.0 + jar + RewriteRunIT#command_line_options + + + 1.8 + 1.8 + UTF-8 + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + +