Skip to content

Commit ab0a77a

Browse files
Add generateRecipeCsv goal (#1113)
* Add generateRecipeCsv goal Add a Maven equivalent of the Gradle recipeCsvGenerate task. The new rewrite:generateRecipeCsv goal scans compiled classes and resources for recipe definitions (both Java and YAML) and writes a recipes.csv marketplace file to src/main/resources/META-INF/rewrite/recipes.csv. The goal delegates to the existing MavenRecipeMarketplaceGenerator from rewrite-maven, merges with any existing CSV to preserve manual entries, and skips pom-packaging projects gracefully. * Add integration test for imperative Java recipe CSV generation Verify that the generateRecipeCsv goal discovers and includes Java class-based Recipe subclasses in the generated CSV, not just YAML declarative recipes. * Fix copyright year * Match goal name with gradle task name to avoid confusion * Rename test resource paths * Fix paths in test resource pom names * Cancel other in progress CI runs for same ref --------- Co-authored-by: Tim te Beek <[email protected]>
1 parent be51df2 commit ab0a77a

7 files changed

Lines changed: 283 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ on:
1414
schedule:
1515
- cron: 0 17 * * *
1616

17+
concurrency:
18+
group: ci-${{ github.ref }}
19+
cancel-in-progress: true
20+
1721
env:
1822
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.gradle_enterprise_cache_username }}
1923
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.gradle_enterprise_cache_password }}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.maven;
17+
18+
import org.apache.maven.plugin.MojoExecutionException;
19+
import org.apache.maven.plugins.annotations.Execute;
20+
import org.apache.maven.plugins.annotations.LifecyclePhase;
21+
import org.apache.maven.plugins.annotations.Mojo;
22+
import org.apache.maven.plugins.annotations.ResolutionScope;
23+
import org.openrewrite.marketplace.RecipeMarketplace;
24+
import org.openrewrite.marketplace.RecipeMarketplaceReader;
25+
import org.openrewrite.marketplace.RecipeMarketplaceWriter;
26+
import org.openrewrite.maven.marketplace.MavenRecipeMarketplaceGenerator;
27+
import org.openrewrite.maven.tree.GroupArtifact;
28+
29+
import java.io.IOException;
30+
import java.nio.charset.StandardCharsets;
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.nio.file.Paths;
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
37+
/**
38+
* Generate a {@code recipes.csv} marketplace file from the recipes found in this project.
39+
* Scans compiled classes and resources in {@code target/classes/} for recipe definitions
40+
* (both Java class-based and YAML declarative recipes) and writes the result to
41+
* {@code src/main/resources/META-INF/rewrite/recipes.csv}.
42+
* <p>
43+
* If an existing {@code recipes.csv} is present, generated data is merged into it,
44+
* preserving any manually added entries.
45+
* <p>
46+
* {@code ./mvnw rewrite:recipeCsvGenerate}
47+
*/
48+
@Execute(phase = LifecyclePhase.PROCESS_CLASSES)
49+
@Mojo(name = "recipeCsvGenerate", requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
50+
public class RecipeCsvGenerateMojo extends AbstractRewriteMojo {
51+
52+
@Override
53+
public void execute() throws MojoExecutionException {
54+
if (rewriteSkip) {
55+
getLog().info("Skipping execution");
56+
return;
57+
}
58+
59+
if ("pom".equals(project.getPackaging())) {
60+
getLog().info("Skipping pom-packaging project");
61+
return;
62+
}
63+
64+
Path classesDir = Paths.get(project.getBuild().getOutputDirectory());
65+
if (!Files.exists(classesDir)) {
66+
getLog().warn("No compiled classes found at " + classesDir + ". Skipping.");
67+
return;
68+
}
69+
70+
String groupId = project.getGroupId();
71+
String artifactId = project.getArtifactId();
72+
73+
List<Path> classpath;
74+
try {
75+
classpath = new ArrayList<>();
76+
for (String element : project.getRuntimeClasspathElements()) {
77+
Path p = Paths.get(element);
78+
if (!p.equals(classesDir)) {
79+
classpath.add(p);
80+
}
81+
}
82+
} catch (Exception e) {
83+
throw new MojoExecutionException("Failed to resolve runtime classpath", e);
84+
}
85+
86+
getLog().info("Generating recipe CSV from: " + classesDir);
87+
getLog().info("Using GAV: " + groupId + ":" + artifactId);
88+
89+
MavenRecipeMarketplaceGenerator generator = new MavenRecipeMarketplaceGenerator(
90+
new GroupArtifact(groupId, artifactId),
91+
classesDir,
92+
classpath
93+
);
94+
RecipeMarketplace generated = generator.generate();
95+
96+
Path outputPath = project.getBasedir().toPath()
97+
.resolve("src/main/resources/META-INF/rewrite/recipes.csv");
98+
99+
RecipeMarketplace marketplace = generated;
100+
if (Files.exists(outputPath)) {
101+
getLog().info("Found existing recipes.csv, merging...");
102+
RecipeMarketplaceReader reader = new RecipeMarketplaceReader();
103+
RecipeMarketplace existing = reader.fromCsv(outputPath);
104+
existing.getRoot().merge(generated.getRoot());
105+
marketplace = existing;
106+
}
107+
108+
RecipeMarketplaceWriter writer = new RecipeMarketplaceWriter();
109+
String csv = writer.toCsv(marketplace);
110+
111+
// Skip if CSV has only a header line (no actual recipes)
112+
int lineCount = 0;
113+
for (int i = 0; i < csv.length(); i++) {
114+
if (csv.charAt(i) == '\n') {
115+
lineCount++;
116+
}
117+
}
118+
if (lineCount <= 1) {
119+
getLog().info("No recipes found, skipping recipes.csv generation");
120+
return;
121+
}
122+
123+
try {
124+
Files.createDirectories(outputPath.getParent());
125+
Files.write(outputPath, csv.getBytes(StandardCharsets.UTF_8));
126+
} catch (IOException e) {
127+
throw new MojoExecutionException("Failed to write recipes.csv", e);
128+
}
129+
130+
getLog().info("Generated recipes.csv at: " + outputPath.toAbsolutePath());
131+
}
132+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.maven;
17+
18+
import com.soebes.itf.jupiter.extension.*;
19+
import com.soebes.itf.jupiter.maven.MavenExecutionResult;
20+
21+
import static com.soebes.itf.extension.assertj.MavenITAssertions.assertThat;
22+
23+
@MavenGoal("${project.groupId}:${project.artifactId}:${project.version}:recipeCsvGenerate")
24+
@MavenJupiterExtension
25+
@MavenOption(MavenCLIOptions.NO_TRANSFER_PROGRESS)
26+
@MavenOption(MavenCLIExtra.MUTE_PLUGIN_VALIDATION_WARNING)
27+
class RecipeCsvGenerateIT {
28+
29+
@MavenTest
30+
void generates_csv_from_yaml_recipe(MavenExecutionResult result) {
31+
assertThat(result)
32+
.isSuccessful()
33+
.project()
34+
.has("src/main/resources/META-INF/rewrite");
35+
assertThat(result).out().info()
36+
.anySatisfy(line -> assertThat(line).contains("Generated recipes.csv"));
37+
}
38+
39+
@MavenTest
40+
void generates_csv_from_java_recipe(MavenExecutionResult result) {
41+
assertThat(result)
42+
.isSuccessful()
43+
.project()
44+
.has("src/main/resources/META-INF/rewrite");
45+
assertThat(result).out().info()
46+
.anySatisfy(line -> assertThat(line).contains("Generated recipes.csv"));
47+
}
48+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xmlns="http://maven.apache.org/POM/4.0.0"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>org.openrewrite.test</groupId>
7+
<artifactId>test-java-recipe-project</artifactId>
8+
<version>1.0.0</version>
9+
<packaging>jar</packaging>
10+
<name>RecipeCsvGenerateIT#generates_csv_from_java_recipe</name>
11+
12+
<properties>
13+
<maven.compiler.source>8</maven.compiler.source>
14+
<maven.compiler.target>8</maven.compiler.target>
15+
</properties>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>org.openrewrite</groupId>
20+
<artifactId>rewrite-core</artifactId>
21+
<version>@rewrite.version@</version>
22+
</dependency>
23+
</dependencies>
24+
25+
<build>
26+
<plugins>
27+
<plugin>
28+
<groupId>@project.groupId@</groupId>
29+
<artifactId>@project.artifactId@</artifactId>
30+
<version>@project.version@</version>
31+
</plugin>
32+
</plugins>
33+
</build>
34+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.openrewrite.test;
2+
3+
import org.openrewrite.ExecutionContext;
4+
import org.openrewrite.Recipe;
5+
import org.openrewrite.TreeVisitor;
6+
7+
public class SampleJavaRecipe extends Recipe {
8+
9+
@Override
10+
public String getDisplayName() {
11+
return "Sample Java recipe";
12+
}
13+
14+
@Override
15+
public String getDescription() {
16+
return "A sample imperative recipe for testing CSV generation.";
17+
}
18+
19+
@Override
20+
public TreeVisitor<?, ExecutionContext> getVisitor() {
21+
return TreeVisitor.noop();
22+
}
23+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xmlns="http://maven.apache.org/POM/4.0.0"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>org.openrewrite.test</groupId>
7+
<artifactId>test-recipe-project</artifactId>
8+
<version>1.0.0</version>
9+
<packaging>jar</packaging>
10+
<name>RecipeCsvGenerateIT#generates_csv_from_yaml_recipe</name>
11+
12+
<properties>
13+
<maven.compiler.source>8</maven.compiler.source>
14+
<maven.compiler.target>8</maven.compiler.target>
15+
</properties>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>org.openrewrite</groupId>
20+
<artifactId>rewrite-core</artifactId>
21+
<version>@rewrite.version@</version>
22+
</dependency>
23+
</dependencies>
24+
25+
<build>
26+
<plugins>
27+
<plugin>
28+
<groupId>@project.groupId@</groupId>
29+
<artifactId>@project.artifactId@</artifactId>
30+
<version>@project.version@</version>
31+
</plugin>
32+
</plugins>
33+
</build>
34+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
type: specs.openrewrite.org/v1beta/recipe
3+
name: org.openrewrite.test.TestRecipe
4+
displayName: Test recipe
5+
description: A test recipe for CSV generation testing.
6+
recipeList:
7+
- org.openrewrite.text.ChangeText:
8+
toText: "Hello World"

0 commit comments

Comments
 (0)