アプリケーションを運用していると、OSSのversion upや脆弱性対応等で複数のコード・アプリケーションに同じ変更を入れていかなければならないことがあります。
変更内容さえ決まってしまえば単純作業をするだけになってしまうのでできれば自動化したいですよね?
それ、OpenRewriteなら実現できます!
この記事では初めてOpenRewriteを使う方向けに導入方法から実際に自動で変更をする方法まで紹介します!
対象
- Gradle
導入手順
まずはGradleアプリケーションにOpenRewriteを導入してみます。
plugins {
...
// OpenRewriteのプラグインを追加
// 最新versionはこちらで確認:https://plugins.gradle.org/plugin/org.openrewrite.rewrite
id("org.openrewrite.rewrite") version("5.34.2")
}
...
rewrite {
activeRecipe(
// ここに書き換えのレシピを追加する
)
}
これだけです!
実行方法
サンプルとして以下のJavaコードを対象に、
自動フォーマッティングをOpenRewriteで対応してみたいと思います。
public class SampleClass {
// 先頭にスペースを入れたい
private String message;
}
目的のレシピを探す
まずは、自動的にフォーマットしてくれるレシピを探します。
https://docs.openrewrite.org/ にアクセスし、検索窓でキーワード検索してみます。
今回はJavaのコードのフォーマットなのでJavaのFormatを選択します。
そうすると関連レシピのリストが表示されるので、
名前から目的を達成できそうなものを選択します。
※ひとつにしぼれなかったら、1つ1つ詳細を確認したり使ってみたりしましょう!
build.gradleを変更
レシピの詳細ページまでたどり着ければ、
具体的な使い方が書かれているのでその通りにbuild.grale
を変更しましょう!
...
rewrite {
activeRecipe("org.openrewrite.java.format.AutoFormat")
}
...
実行
レシピの設定が終わったらあとは実行するだけです!
OpenRewriteではDryrun(※)が用意されているので、事前に実行しておくとよいです!
(※)Dryrunは仮実行をして実行結果だけを確認するためのものです。
$ ./gradlew rewriteDryRun
実行結果は build/reports/rewrite/rewrite.patch
に出力されています!
今回のコードだとこんな感じ
public class SampleClass {
-private String message;
+ private String message;
}
問題なければ本実行をしましょう!
$ ./gradlew rewriteRun
ここまでで、一通り実行ができるようになりました!
ここからは実用性のあることをもう少し紹介します!
レシピの管理
先ほどはactiveRecipe
でレシピを設定するだけでレシピの登録ができました。
しかし、ものによってはrewrite.yml
が必要になるケースがあります。
例えばyamlファイルを更新できる以下のレシピです。
このレシピを利用しようとすると、まずプロジェクト直下にrewrite.yml
を用意し
type: specs.openrewrite.org/v1beta/recipe
name: sample.SampleRecipe
recipeList:
- org.openrewrite.yaml.ChangeValue:
oldKeyPath: $.path1.path2
value: value2
name
で指定した識別子をactiveRecipe
に設定します。
rewrite {
activeRecipe("sample.SampleRecipe")
}
このようにrewrite.yml
が必要になるケースがあるのでそれだったら最初からrewrite.yml
でレシピを管理しておいた方が良いよねと思っています。
ちなみにレシピは以下のように複数設定することができます!
type: specs.openrewrite.org/v1beta/recipe
name: sample.SampleRecipe
recipeList:
- org.openrewrite.yaml.ChangeValue:
oldKeyPath: $.path1.path2
value: value2
- org.openrewrite.java.format.AutoFormat
独自レシピの開発
検索してもやりたいことを実現できるレシピが見つからない場合、独自にレシピを開発することも可能です!
ここでは特定のクラスにhello
メソッドを追加するレシピを自作してみようと思います。
準備
独自レシピはgradleプロジェクトでJavaで開発をします。
Gradleプロジェクトを作成した上で、build.gradle
に変更を加えます。
plugins {
...
// maven repositoryにアップロードするためのプラグイン
id 'maven-publish'
}
dependencies {
...
// Java関連のレシピ開発に必要
implementation("org.openrewrite:rewrite-java:7.35.0")
runtimeOnly("org.openrewrite:rewrite-java-8:7.35.0")
runtimeOnly("org.openrewrite:rewrite-java-11:7.35.0")
runtimeOnly('org.openrewrite:rewrite-java-17:7.35.0')
// Maven関連のレシピ開発に必要
// implementation("org.openrewrite:rewrite-maven:7.35.0")
// Yaml関連のレシピ開発に必要
// implementation("org.openrewrite:rewrite-yaml:7.35.0")
// Properties関連のレシピ開発に必要
// implementation("org.openrewrite:rewrite-properties:7.35.0")
// XML関連のレシピ開発に必要
// implementation("org.openrewrite:rewrite-xml:7.35.0")
// レシピのテストに必要
// testImplementation("org.openrewrite:rewrite-test:7.35.0")
}
// mavenリポジトリへのアップロードの設定
publishing {
publications {
mavenJava(MavenPublication) {
groupId = 'sample'
artifactId = 'openrewrite-sample'
from components.java
}
}
}
開発
次にレシピの中身、書き換え内容の実装です!
package sample.recipe;
public class AddHelloMethod extends Recipe {
// 表示名なので任意の文字列でOK
@Override
public String getDisplayName() {
return "Add Hello Method";
}
// Visitorクラスを実装して返却する
@Override
protected JavaIsoVisitor<ExecutionContext> getVisitor() {
return new AddHelloMethodVisitor();
}
// Visitorクラス(書き換えの具体内容を実装するクラス)
public class AddHelloMethodVisitor extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
// 指定したクラス以外はスキップ
if (!classDecl.getSimpleName().equals("SampleClass")) {
return classDecl;
}
// helloメソッドの存在を確認
boolean helloMethodExists = classDecl.getBody().getStatements().stream()
.filter(statement -> statement instanceof J.MethodDeclaration)
.map(J.MethodDeclaration.class::cast)
.anyMatch(methodDeclaration -> methodDeclaration.getName().getSimpleName().equals("hello"));
// helloメソッドが存在する場合はスキップ
if (helloMethodExists) {
return classDecl;
}
// 挿入するコードを定義
var helloTemplate = JavaTemplate.builder(this::getCursor, """
public String hello() {
return "Hello, OpenRewrite!!!";
}
""")
.build();
// クラスの最後尾に定義したコードを挿入
classDecl = classDecl.withBody(
classDecl.getBody().withTemplate(helloTemplate, classDecl.getBody().getCoordinates().lastStatement())
);
return classDecl;
}
}
}
(書き換えたい内容によって実装方法がいろいろありそうなので別記事でまとめたい、、、)
レシピの定義
次に作成したクラスをOpenRewriteが認識できるように設定をします。
設定はsrc/main/resources/META-INF/rewrite/rewrite.yml
で行います。
# 固定
type: specs.openrewrite.org/v1beta/recipe
# レシピの識別子。利用側の「recipeList」で指定する
name: sample.SampleRecipe
recipeList:
# 定義したクラスの「パッケージ+クラス名」を指定する
- sample.recipe.AddHelloMethod
Maven Repositoryへアップロード
最後にMaven Repositoryへアップロードしておしまいです!
$ ./gradlew publish
# 個人開発やお試し中はローカルリポジトリを使うのもあり
# $ ./gradlew publichToMavenLocal
利用側の設定
dependencies {
...
// rewriteという独自の関数で依存を追加
rewrite('sample:openrewrite-sample:+')
}
recipeList:
# 独自レシピの「name」を指定
- sample.SampleRecipe