わたしが書いた Publishing Custom Gradle Plugin explained step by step の日本語版です。
はじめに
この記事のサンプルコードを格納したGitHubレポジトリがあります。URLは下記の通り。
この記事を書くのに Gradle 8.0.2, Java 17.0.7, macOS 12.6 を使いました。
要旨
本記事でわたしはカスタムGradleプラグインをGradle Plugin Portalで公開するまでの作業手順を説明します。サンプルコード一式を提供します。
わたしはGradle公式ドキュメントのうち下記ページを読むことから始めました。
公式ドキュメンメントには詳細な情報が山ほど書いてあります。わたしは公式ドキュメントを読み漁り、コードを書いて試すこともしたがよくわからなかった。私見ですが、公式ドキュメントには表側のことすなわちbuild.gradleスクリプトの書き方とコマンドの叩き方が書いてあるが、説明が不十分だ。裏側のことすなわちbuild.gradleスクリプトを記述してコマンドを実行した結果としてどういうファイルが生成されるのか、生成されたファイルに何が書いてあるかをまったく説明しない。だからドキュメントから得られた情報を頭の中で組み立ててひとつのシステムとしてイメージすることがむずかしかった。
そこでわたしは自作したカスタムGradleプラグインをGradle Plugin Portalに公開する手順を8つの段階に分けた。ひとつの段階でひとつの疑問に対する答えを導き出すことに集中した。各ステップにおいて java-gradle-plugin
と maven-publish
と com.gradle.plugin-publish
プラグインが <projectDir>/build
ディレクトリの中にどんな物(ディレクトリとファイル)を生成するのかを検証した。さまざまのコマンドを実行する都度、<projectDir>/build
ディレクトリの中に生成されたファイルのツリーを眺め、ファイルをエディタで開き内容を理解しようとした。
段階的な分析の成果としてわたしが理解したことをこの記事で説明します。Gradleの公式ドキュメントの森の中を彷徨っている誰かの助けになればいいなと願って。
step1 はじめにGroovyコードありき
step1
という名前のGradleプロジェクトを作り、その中にカスタムGradleプラグインとなるはずのGroovyコードを書きました。
settings.gradle と build.gradle
レポジトリ のルートディレクトリの下に step1
ディレクトリを作り、Gradleプロジェクトとして必須であるファイル群を作った。 step1/settings.gradle
と step1/build.gradle
を作った。
step1/settings.gradle
rootProject.name = 'step1'
step1/build.gradle
plugins {
id 'groovy'
}
group = 'com.example'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
// Use the awesome Spock testing and specification framework
testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0'
}
tasks.named('test') {
// Use JUnit Jupiter for unit tests.
useJUnitPlatform()
}
カスタムGradleプラグインのGroovyコード
Groovyコード step1/src/main/groovy/com/example/greeting/GreetingPlguin
を書いた。
package com.example.greeting
import org.gradle.api.Plugin
import org.gradle.api.Project
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println "Hello from GreetingPlugin"
}
}
project.task('goodbye') {
doLast {
println "Goodbye from GreetingPlugin"
}
}
}
}
この com.example.greeting.GreetingPlugin
プラグインをGradleビルドに適用するとhello
タスクとgoodbye
タスクが挿入される。helloタスクを実行してみよう。こうなる。
$ gradlew hello
Hello from GreetingPlugin
つまらないものですがカスタムGradleプラグインです。
JUnit5によるテスト
テストコード step1/src/test/groovy/com/example/greeting/GreetingPluginTest.groovy
も書いた。
package com.example.greeting
import org.gradle.api.Project
import org.gradle.testfixtures.ProjectBuilder
import spock.lang.Specification
class GreetingPluginTest extends Specification {
def "the plugin registers the tasks"() {
given:
Project project = ProjectBuilder.builder().build()
when:
project.plugins.apply("org.sample.Greetings")
then:
project.tasks.findByName("hello") != null
project.tasks.findByName("goodbye") != null
}
}
本記事は全部で8つの段階から構成されますが、Groovyコードはほとんど変更しません。上記のコードによって出来るPluginをどうやってパブリッシュするのか、その手順を分析することに集中します。
ビルドを実行すると何が起きるか
ビルドを実行してGroovyコードをコンパイルしようとした。しかし失敗した。
GroovyコードがGradle APIに属するclass(たとえば prg.gradle.api.Project
)を参照しているが、コンパイラがGradle APIにアクセスできなかったのでエラーになった。
何をしなければならないのか
このGroovyコードをコンパイルするためには java-gradle-plugin
プラグインをこの build.gradle に適用する必要がある。次のstep2でそれをします。
step2 java-gradle-pluginプラグインを適用する
java-gradle-plugin
プラグインをビルドに適用します。これによってGradle APIがアクセス可能になってコンパイルできるようになる。この段階で step2/build
ディレクトリの下にどんなファイルが生成されるかを観察する。特にjava-gradle-plugin
が step2/build/pluginDescriptor
を生成することに注目する。
settings.gradle と build.gradle
step2/settings.gradle
ファイルと step2/build.gradle
ファイルを作った。内容は下記の通り。
step2/settings.gradle
rootProject.name = 'step2'
step2/build.gradle
リンク step2/build.gradle をクリックしてコードを参照してください。
特に3行目に注目。java-gradle-plugin
プラグインをこのビルドに適用している。
id 'java-gradle-plugin'
24行目の plugins
クロージャにも注目。ここで plugin id
を宣言し、カスタムGradleプラグインを実装しているclassのFully Qualified Nameを宣言している。
// Define the plugin
plugins {
MyGreeting {
id = 'org.sample.Greetings'
implementationClass = 'com.example.greeting.GreetingPlugin'
}
}
}
ここで注目してほしいのが、plugin id と実装classのパッケージ名が違っていてもかまわないということだ。例示するためわざと plugin id を org.sample.Greetings
と宣言し、class名を com.example.greeting.GreeingPlugin
と宣言した。これでOKなのだ。
Gradle公式ドキュメントの下記の箇所にplugin idをどのように決めるのがオススメかが書いてある。一読のこと。
ビルドを実行すると何が起きるか
step2のビルドを実行するとGroovyコードをコンパイルできた。JUnitテストを実行して成功した。
java-gradle-plugin が何を生成するか
step2/build
ディレクトリの中を観察してみよう。java-gradle-plugin
プラグインがたくさんのファイルを生成しているのがわかる。
build ディレクトリの外観
まず build
ディレクトリのツリーの形を見てみよう。
$ tree ./build
./build
├── classes
│ └── ...
├── generated
│ └── ...
├── pluginDescriptors
│ └── org.sample.Greetings.properties
├── pluginUnderTestMetadata
│ └── ...
├── reports
│ └── ...
├── resources
│ └── main
│ └── META-INF
│ └── gradle-plugins
│ └── org.sample.Greetings.properties
├── test-results
│ └── ...
└── tmp
├── ...
47 directories, 20 files
この中で下記のファイルが重要だ:
step2/build/pluginDescriptors/org.sample.Greetings.properties
implementation-class=com.example.greeting.GreetingPlugin
中身が同じファイルが別の場所にもある。
step2/build/resources/main/META-INF/gradle-plugins/org.sample.Greetings.properties
implementation-class=com.example.greeting.GreetingPlugin
これは何か? このpropertiesファイルは最終的にjarファイルの中に格納されて配布される。使い手のビルドがそのjarファイルを取り込んだとき、カスタムプラグインの実装クラスがどれであるかを教える名札のようなものだ。Gradle公式ドキュメントの下記URLを参照のこと。
次に何をするか
プラグインをコンパイルできた。次にプラグインをpublishしたい。コマンドを試してみると、当然のことながら、失敗した。
$ cd step2
:~/github/PublishingCustomGradlePlugin_StepByStep/step2 (develop *)
$ gradle publish
FAILURE: Build failed with an exception.
* What went wrong:
Task 'publish' not found in root project 'step2'.
...
step3 maven-publish プラグインを適用する
第3段階としてmaven-publish
プラグインを適用します。プロジェクトの build ディレクトリの中にMavenレポジトリを作り、そこにカスタムプラグインをpublishします。maven-publish
プラグインが裏側で何をするのかを観察します。
settings.gradle と build.gradle
step3/settings.gradle
ファイルと step3/build.gradle
ファイルを作りました。中身は次の通り。
step3/settings.gradle
rootProject.name = 'step3'
step3/build.gradle
リンク step3/build.gradle を参照してください。
5行目で mave-publish
プラグインを適用することを宣言しています。
plugins {
...
id 'maven-publish'
}
35行目に publishing
extention クロージャがあって、その中でローカルなMavenレポジトリを作ることを宣言しています。これによってstep3/build/repos-maven
にディレクトリが作られます。
repositories {
maven {
name = "sketch"
url = layout.buildDirectory.dir("repos-maven")
}
}
}
ビルドを実行すると何が起きるか
gradle publish
タスクを実行した。
$ pwd
/Users/kazuakiurayama/github/PublishingCustomGradlePlugin_StepByStep/step3
:~/github/PublishingCustomGradlePlugin_StepByStep/step3 (develop *+)
$ gradle clean
BUILD SUCCESSFUL in 850ms
1 actionable task: 1 executed
:~/github/PublishingCustomGradlePlugin_StepByStep/step3 (develop *+)
$ gradle publish
BUILD SUCCESSFUL in 881ms
8 actionable tasks: 4 executed, 4 up-to-date
Successしています。でもコンソールメッセージを見ただけでは舞台裏で何が起きたのか皆目分かりません。
maven-publish プラグインが何を生成したのか
build
ディレクトリのツリーの様子
build
ディレクトリの中に幾つかのディレクトリが作られ、たくさんのファイルが maven-publish
によって生成されています。
:~/github/PublishingCustomGradlePlugin_StepByStep/step3 (develop *+)
$ tree build
build
├── classes
│ └── ...
├── generated
│ └── ...
├── libs
│ └── step3-1.0.jar
├── pluginDescriptors
│ └── org.sample.Greetings.properties
├── publications
│ ├── MyGreetingPluginMarkerMaven
│ │ └── pom-default.xml
│ └── pluginMaven
│ └── pom-default.xml
├── repos-maven
│ ├── com
│ │ └── example
│ │ └── step3
│ │ ├── 1.0
│ │ │ ├── step3-1.0.jar
│ │ │ ├── step3-1.0.jar.md5
│ │ │ ├── step3-1.0.jar.sha1
│ │ │ ├── step3-1.0.jar.sha256
│ │ │ ├── step3-1.0.jar.sha512
│ │ │ ├── step3-1.0.pom
│ │ │ ├── step3-1.0.pom.md5
│ │ │ ├── step3-1.0.pom.sha1
│ │ │ ├── step3-1.0.pom.sha256
│ │ │ └── step3-1.0.pom.sha512
│ │ ├── maven-metadata.xml
│ │ ├── maven-metadata.xml.md5
│ │ ├── maven-metadata.xml.sha1
│ │ ├── maven-metadata.xml.sha256
│ │ └── maven-metadata.xml.sha512
│ └── org
│ └── sample
│ └── Greetings
│ └── org.sample.Greetings.gradle.plugin
│ ├── 1.0
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom.md5
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom.sha1
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom.sha256
│ │ └── org.sample.Greetings.gradle.plugin-1.0.pom.sha512
│ ├── maven-metadata.xml
│ ├── maven-metadata.xml.md5
│ ├── maven-metadata.xml.sha1
│ ├── maven-metadata.xml.sha256
│ └── maven-metadata.xml.sha512
├── resources
│ └── main
│ └── META-INF
│ └── gradle-plugins
│ └── org.sample.Greetings.properties
└── tmp
├── ...
37 directories, 38 files
いろんなものがあって、ちょっとよく分かりません。一つひとつ見ていきましょう。
プラグインのbinaryを格納したjarファイル
step2/build/libs/step3-1.0.jar
ができています。jarファイルの中身を見てみましょう。
:~/github/PublishingCustomGradlePlugin_StepByStep/step3 (develop *+)
$ tar -xvf build/libs/step3-1.0.jar
x META-INF/
x META-INF/MANIFEST.MF
x com/
x com/example/
x com/example/greeting/
x com/example/greeting/GreetingPlugin$_apply_closure2$_closure4.class
x com/example/greeting/GreetingPlugin$_apply_closure2.class
x com/example/greeting/GreetingPlugin$_apply_closure1$_closure3.class
x com/example/greeting/GreetingPlugin.class
x com/example/greeting/GreetingPlugin$_apply_closure1.class
x META-INF/gradle-plugins/
x META-INF/gradle-plugins/org.sample.Greetings.properties
わたしが書いたカスタムGradleプラグインのclassファイル com.example.greeting.GreetingPlugin
が格納されています。それからMETA-INFの下に plugin id `org.sample.Greeting に対応するpropertiesファイルも格納されています。jarファイルの内容はこれで良さそうです。
jarファイルの名前がどのように決まるのか
jarファイルの名前は step3-1.0.jar
です。この名前がどうやって決まったのでしょうか?
実はGradleビルドプロジェクトの rootProject.name
プロパティの値が step3
と指定されている。この値がjarファイルのbase nameのデフォルトになります。具体的には step3/settings.gradle
にそう書いてあるから。バージョン番号はstep3/build.gradle
ファイルの中で project.version
プロパティが 1.0
と設定されていることによっています。
Mavenレポジトリの中に何ができているか
step3/build.gradle
ファイルの中にこう書きました。
gradlePlugin {
// Define the plugin
plugins {
MyGreeting {
id = 'org.sample.Greetings'
implementationClass = 'com.example.greeting.GreetingPlugin'
}
}
}
publishing {
repositories {
maven {
url = layout.buildDirectory.dir("repos-maven")
}
}
}
この記述によって Mavenレポジトリが step3/build/repos-maven
ディレクトリに作られ、その中に下記のようなファイル群が生成されました。
$ tree build
build
├── publications
│ ├── MyGreetingPluginMarkerMaven
│ │ └── pom-default.xml
│ └── pluginMaven
│ └── pom-default.xml
...
│ └── org
│ └── sample
│ └── Greetings
│ └── org.sample.Greetings.gradle.plugin
│ ├── 1.0
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom
...
build/publications/MyGreetingPluginMarkerMaven
という長い名前のディレクトリができています。これは何なんでしょうか? このディレクトリ名を節に分けると
'MyGreeting' + 'PluginMarker' + 'Maven'
という3つの部分から構成されています。すなわち
<publication name>
+ 'PluginMarker' + <type of repository>
という意味と解釈することができます。でも "PluginMarker" って何だ? --- 公式ドキュメントに説明がちょっと書いてあります。
- linke:https://docs.gradle.org/current/userguide/plugins.html#sec:plugin\_markers\[Plugin Marker Artifacts]
Since the plugins {} DSL block only allows for declaring plugins by their globally unique plugin id and version properties, Gradle needs a way to look up the coordinates of the plugin implementation artifact. To do so, Gradle will look for a Plugin Marker Artifact with the coordinates plugin.id:plugin.id.gradle.plugin:plugin.version. This marker needs to have a dependency on the actual plugin implementation. Publishing these markers is automated by the java-gradle-plugin.
うーん。
Gradle needs a way to look up the coordinates of the plugin implementation artifact
って何を言っているのでしょう? buildディレクトリの中のファイルを観察して具体的細部を発見しよう。
step3/build/publications/MyGreetingPluginMarkerMaven/pom-default.xml
ファイルの中身がこうなっています。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.sample.Greetings</groupId>
<artifactId>org.sample.Greetings.gradle.plugin</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>step3</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
このXMLファイルはMavenプロジェクトが定義するところの Project Object Model (POM) に他なりません。build.gradleの中で plugin id として指定した org.sample.Greetings
というシンボルが、POMの中では <groupId>org.sample.Greetings</groupId>
という形式に変換されているのがわかる。そして<artifactId>org.sample.Greetings.gradle.plugin</artifactId>
と <version>1.0</version>
というコードが生成されている。
build.gradleファイルに書く
plugins {
id 'org.sample.Greetings' version '1.0'
}
という記述形式は極めて簡潔です。ユーザの使い勝手のためには簡潔な記述は好ましい。そのいっぽうで jarファイルをMavenレポジトリに格納して配布可能にすることも達成したい。そのためにはPOMを生成してjarファイルに添付することが必須要件になる。ならば開発者がPOM.xmlファイルを手書きすればいいようなものだが、POMはコードが長くて間違えやすいから、厄介な仕事だ。そこで maven-publish
プラグインは plugin id とimplementationClass を指定することだけを開発者に求めて、それを材料として、冗長なPOMファイルへ翻訳するという手間仕事を代行してくれる。その手間仕事の成果物のことをmaven-publish
プラグインは PluginMarker という集合名で呼ぶことにした。上記の例に則していうなれば MyPlugin
という publication nameを持つ PluginMaker が生成された、ということができます。
pom-default.xml
ファイルの中身の後半部分を観察しましょう。<dependency>
の記述があって、org.sample.Greetings.gradle.plugin
というartifact idが別のartifactに依存していることが表明されている。依存先のartifactは com.example:step3:1.0
という coordinate (座標?) によって表現されている。これに対応するのが step3/build/publications/pluginMaven/pom-default.xml
です。その中身は下記の通り。
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>step3</artifactId>
<version>1.0</version>
</project>
このPOMが指し示すのはバイナリclassファイルを格納したjarファイルです。
$ tree build
build
...
├── repos-maven
│ ├── com
│ │ └── example
│ │ └── step3
│ │ ├── 1.0
│ │ │ ├── step3-1.0.jar
...
つまりこういうことだ。 maven-publish
プラグインは一つのカスタムGradleプラグインに対応して必ず2つのモノを生成してMavenレポジトリにパブリッシュする。第一にplugin自身の POM xml ファイル。第二にpluginの実装classを格納したjarファイル。plugin idからjarへの結びつきを表現したPOMを生成する仕事を maven-plugin
がやってくれる。
わたしが自作した org.sample.Greetings
プラグインを誰かが自分のGradleビルに適用したとしましょう。彼はGradleビルドにこう書く。
plugins {
id 'org.sample.Greeting' version "1.0"
}
ビルドはGaven Plugin Pluginにリクウエストを投げる。このplugin idとversionの組み合わせに対応するものがMavenレポジトリにあるとすれば、サブディレクトリ
org/sample/Greetings/org.sample.Greetings.gradle.plugin/1.0/org.sample.Greetings.gradle.plugin-1.0.pom
にあるはずだ。この対応関係は maven-publish
プラグインが決めたルールであり、設定変更可能ではない。
ユーザのGradleビルドはpluginのPOM.xmlを発見したら中身を解析する。するとclassファイルを格納したjarへの<dependency>
を発見するだろう。だからGradleビルドは外部依存性を解決するいつもの仕事を遂行する。かくして彼のGradleビルドはわたしのカスタムGradleプラグインの実装classを発見して利用できるようになる。これにて一件落着。
わたしは最初、カスタムGradleプラグインをpublishするとき、何かjarファイルが1個生成されてそれがサイトにアップロードされるだけと想像していた。実はそんなに単純ではなかった。pluginのPOMとjarと2つのモノが生成されてMavenレポジトリにアップロードされる。その2つの依存関係がPOMに書いてある。maven-publish
プラグインは裏方仕事をやってくれている。
"publish XXX" タスク群
maven-publish
プラグインを適用すると幾つかのタスクがビルドに挿入される。それらタスクは "publish XXXX" という形の名前を持っている。それらタスクを一覧するには gradle tasks | grep publish
コマンドを実行すればいい。
$ gradle tasks | grep publish
publish - Publishes all publications produced by this project.
publishAllPublicationsToSketchRepository - Publishes all Maven publications produced by this project to the sketch repository.
publishMyGreetingPluginMarkerMavenPublicationToMavenLocal - Publishes Maven publication 'MyGreetingPluginMarkerMaven' to the local Maven repository.
publishMyGreetingPluginMarkerMavenPublicationToSketchRepository - Publishes Maven publication 'MyGreetingPluginMarkerMaven' to Maven repository 'sketch'.
publishPluginMavenPublicationToMavenLocal - Publishes Maven publication 'pluginMaven' to the local Maven repository.
publishPluginMavenPublicationToSketchRepository - Publishes Maven publication 'pluginMaven' to Maven repository 'sketch'.
publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.
うわー、字が多い。パッと見に何が書いてあるのか読み取れない。テキストにスタイルを加えて出来るだけ読みやすくしてみよう。
次の3つのタスクは一種のマクロだ。
-
publish
- Publishes all publications produced by this project. -
publishAllPublicationsToSketchRepository
- Publishes all Maven publications produced by this project to thesketch
repository. -
publishToMavenLocal
- Publishes all Maven publications produced by this project to the local Maven cache.
ほかのタスクはマクロではなくてそれぞれ完結した役目を持つタスクだ。
-
publishMyGreetingPluginMarkerMavenPublicationToMavenLocal
- Publishes Maven publicationMyGreetingPluginMarkerMaven
to the local Maven repository. -
publishMyGreetingPluginMarkerMavenPublicationToSketchRepository
- Publishes Maven publicationMyGreetingPluginMarkerMaven
to Maven repositorysketch
. -
publishPluginMavenPublicationToMavenLocal
- Publishes Maven publicationpluginMaven
to the local Maven repository. -
publishPluginMavenPublicationToSketchRepository
- Publishes Maven publicationpluginMaven
to Maven repositorysketch
.
Publications とは何か
タスク名を観察すると、その中で二つのシンボルが使われているのがわかる。すなわち MyGreetingPluginMarkerMaven
と pluginMaven
だ。maven-publish
プラグインはこの名前を publication (出版物?) と集合的に呼んでいる。publicationの名前は build/publications
ディレクトリの下にあるサブディレクトリの名前と同じだ。
$ tree build
build
├── publications
│ ├── MyGreetingPluginMarkerMaven
│ │ └── pom-default.xml
│ └── pluginMaven
│ └── pom-default.xml
...
MyGreetingPluginMarketMaven
というpublicationの中にカスタムGradleプラグインのメタデータを記述したPOM.xmlファイルがある。
pluginMaven
というpublicationの中には実装クラスを格納したjarファイルのメタデータを記述したPOMファイルがある。その中をみるとjarファイルの名前が step3-1.0.jar
と記述してある。gradle publshPluginMavenPublicationToXXXX
コマンドを実行すると maven-publish
プラグインはjarファイルを生成する仕事を自動的に行う。開発者はjarを生成するためのtaskをbuild.gradleの中に明示的にコーディングする必要がないし、仮に明示的にコーディングしたとしても、そうやって作られたjarファイルをmaven-publish
プラグインは無視する。だからやっても無駄だ。
Mavenレポジトリ "MavenLocal" と "Sketch" について
maven-publish
プラグインを適用することで挿入されたタスクの名前をみると、2つのシンボルが使われているのが見てとれる。すなわち "MavenLocal" と "Sketch" だ。
Gradleに慣れた人なら "local Maven repository" とか "local Maven cache" が何を意味するかは既にご存知のはず。Baeldung, Where is the Maven Local Repository?とかを一読して思い出してください。
"Sketch"というシンボルは私が build.gradle
ファイルにレポジトリのnameとして指定したものだ。下記のように:
publishing {
repositories {
maven {
name = "sketch"
さて、以上の解釈を踏まえると "publishXXXX" タスクが何をするのかその名前から推測できるようになった。
step4 MavenレポジトリとIvyレポジトリ
Gradleは二種類のレポジトリをサポートしている。MavenレポジトリとIvyレポジトリだ。step3ではMavenレポジトリにパブリッシュしてみた。step4では、Mavenレポジトリに加えて、Ivyレポジトリにもパブリッシしてみよう。
settings.gradle と build.gradle
step4/settings.gradle
ファイルと step4/build.gradle
ファイルを作った。
step4/settings.gradle
rootProject.name = 'step4'
step4/build.gradle
リンク step4/build.gradle を参照のこと。
6行目で ivy-publish
プラグインを適用している。ivy-publish
はmaven-publish
の同類と思えばいい。
plugins {
...
id 'maven-publish'
id 'ivy-publish'
}
36行目に publishing
extension クロージャを記述している。そこでIvyレポジトリをどこに作るかを指定している。
publishing {
repositories {
maven {
name = "sketchMaven"
url = layout.buildDirectory.dir("repos-maven")
}
ivy {
name = "sketchIvy"
url = layout.buildDirectory.dir("repos-ivy")
}
}
}
ビルドを実行すると何が起こるか
:~/github/PublishingCustomGradlePlugin_StepByStep/step4 (develop *+)
$ gradle publish
BUILD SUCCESSFUL in 881ms
8 actionable tasks: 4 executed, 4 up-to-date
静かにビルドが成功しました。しかし何が起こったのか、メッセージからは何もわからない。build
ディレクトリの中身の変化を観察しましょう。
build
ディレクトリのツリー
gradle publsh
コマンドを実行すると build
ディレクトリにたくさんのファイルが生成されました。
:~/github/PublishingCustomGradlePlugin_StepByStep/step4 (develop *)
$ tree build
build
├── classes
│ └── ...
├── generated
│ └── ...
├── libs
│ └── step4-1.0.jar
├── pluginDescriptors
│ └── org.sample.Greetings.properties
├── pluginUnderTestMetadata
│ └── plugin-under-test-metadata.properties
├── publications
│ ├── MyGreetingPluginMarkerIvy
│ │ └── ivy.xml
│ ├── MyGreetingPluginMarkerMaven
│ │ ├── org.sample.Greetings.gradle.plugin (1).iml
│ │ └── pom-default.xml
│ ├── pluginIvy
│ │ └── ivy.xml
│ └── pluginMaven
│ ├── pom-default.xml
│ └── step4.iml
├── reports
│ └── ...
├── repos-ivy
│ ├── com.example
│ │ └── step4
│ │ └── 1.0
│ │ ├── ivy-1.0.xml
│ │ ├── ivy-1.0.xml.sha1
│ │ ├── ivy-1.0.xml.sha256
│ │ ├── ivy-1.0.xml.sha512
│ │ ├── step4-1.0.jar
│ │ ├── step4-1.0.jar.sha1
│ │ ├── step4-1.0.jar.sha256
│ │ └── step4-1.0.jar.sha512
│ └── org.sample.Greetings
│ └── org.sample.Greetings.gradle.plugin
│ └── 1.0
│ ├── ivy-1.0.xml
│ ├── ivy-1.0.xml.sha1
│ ├── ivy-1.0.xml.sha256
│ └── ivy-1.0.xml.sha512
├── repos-maven
│ ├── com
│ │ └── example
│ │ └── step4
│ │ ├── 1.0
│ │ │ ├── step4-1.0.jar
│ │ │ ├── step4-1.0.jar.md5
│ │ │ ├── step4-1.0.jar.sha1
│ │ │ ├── step4-1.0.jar.sha256
│ │ │ ├── step4-1.0.jar.sha512
│ │ │ ├── step4-1.0.pom
│ │ │ ├── step4-1.0.pom.md5
│ │ │ ├── step4-1.0.pom.sha1
│ │ │ ├── step4-1.0.pom.sha256
│ │ │ └── step4-1.0.pom.sha512
│ │ ├── maven-metadata.xml
│ │ ├── maven-metadata.xml.md5
│ │ ├── maven-metadata.xml.sha1
│ │ ├── maven-metadata.xml.sha256
│ │ └── maven-metadata.xml.sha512
│ └── org
│ └── sample
│ └── Greetings
│ └── org.sample.Greetings.gradle.plugin
│ ├── 1.0
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom.md5
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom.sha1
│ │ ├── org.sample.Greetings.gradle.plugin-1.0.pom.sha256
│ │ └── org.sample.Greetings.gradle.plugin-1.0.pom.sha512
│ ├── maven-metadata.xml
│ ├── maven-metadata.xml.md5
│ ├── maven-metadata.xml.sha1
│ ├── maven-metadata.xml.sha256
│ └── maven-metadata.xml.sha512
├── resources
│ └── main
│ └── META-INF
│ └── gradle-plugins
│ └── org.sample.Greetings.properties
├── test-results
│ └── ...
...
80 directories, 68 files
MavenレポジトリとIvyレポジトリと二種類を生成したのでディレクトリとファイルの数が増えたが、概念的には変化なし。複雑性は増していない。
ちなみにディレクトリ名を見比べると面白いことがわかります。
-
step4/build/publications/MyGreetingPluginMarkerIvy
-
step4/build/publications/MyGreetingPluginMarkerMaven
-
step4/build/publications/pluginIvy
-
step4/build/publications/pluginMaven
コマンドの名前が対称形を成している。もしもレポジトリの新種が発明されて、その名前が Bar だったとしたら、きっと bar-publish
プラグインが開発されるだろう。そして bar-publish
プラグインは下記のディレクトリを生成するでしょう。
-
step4/build/publications/MyGreetingPluginMarkerBar
-
step4/build/publications/pluginBar
シンプルですね。
step5 複数のプラグインを1個のプロジェクトがpublishするケース
カスタムGradleプラグインをパブリッシュする手順を述べた記事はいくつも公開されているけれど、それら記事はたいてい1個のプロジェクトが1個のプラグインをpublishする例を述べている。そこでわたしは疑問に思った。1つのプロジェクトが2つ以上のカスタムGradleプラグインをpublishすることができるのだろうか?やってみたら成功した。
プラグイン2個分のGroovyソース
step4のサンプルにはGroovyクラスが1つだけある。すなわち com.example.greeting.GreetingPlugin
が2つのメソッド hello()
と goodbye()
を実装している。
このstep5ではGreetingPluginを2つのクラスに切り分けよう。すなわち
だからstep5プロジェクトはカスタムGradleプラグインを2つパブリッシュすることになる。
settings.gradle と build.gradle
step5/settings.gradle
ファイルと step5/build.gradle
ファイルを作った。
step5/settings.gradle
rootProject.name = 'step5'
step5/build.gradle
リンク step5/build.gradle を参照のこと。
36行目に gradlePlugin
extension クロージャを記述した。そこで2つのプラグインを宣言した。すなわちorg.sample.Hello
と org.sample.Goodbye
です。
gradlePlugin {
// Define the plugin
plugins {
MyHello {
id = 'org.sample.Hello'
implementationClass = 'com.example.hello.HelloPlugin'
}
MyGoodbye {
id = 'org.sample.Goodbye'
implementationClass = 'com.example.goodbye.GoodbyePlugin'
}
}
}
ビルドを実行すると何が起きるか
:~/github/PublishingCustomGradlePlugin_StepByStep/step5 (develop *)
$ gradle clean
BUILD SUCCESSFUL in 895ms
1 actionable task: 1 executed
:~/github/PublishingCustomGradlePlugin_StepByStep/step5 (develop *)
$ gradle test
BUILD SUCCESSFUL in 7s
$ gradle publish
BUILD SUCCESSFUL in 2s
10 actionable tasks: 10 executed
ビルドは静かに成功しました。
build
ディレクトリに生成されたファイル
$ cd step5
$ tree build
build
├── classes
│ └── ...
├── generated
│ └── ...
├── libs
│ └── step5-1.0.jar
├── pluginDescriptors
│ ├── org.sample.Goodbye.properties
│ └── org.sample.Hello.properties
├── pluginUnderTestMetadata
│ └── ...
├── publications
│ ├── MyGoodbyePluginMarkerMaven
│ │ └── pom-default.xml
│ ├── MyHelloPluginMarkerMaven
│ │ └── pom-default.xml
│ └── pluginMaven
│ └── pom-default.xml
├── reports
│ └── ...
├── repos-maven
│ ├── com
│ │ └── example
│ │ └── step5
│ │ ├── 1.0
│ │ │ ├── step5-1.0.jar
│ │ │ ├── step5-1.0.jar.md5
│ │ │ ├── step5-1.0.jar.sha1
│ │ │ ├── step5-1.0.jar.sha256
│ │ │ ├── step5-1.0.jar.sha512
│ │ │ ├── step5-1.0.pom
│ │ │ ├── step5-1.0.pom.md5
│ │ │ ├── step5-1.0.pom.sha1
│ │ │ ├── step5-1.0.pom.sha256
│ │ │ └── step5-1.0.pom.sha512
│ │ ├── maven-metadata.xml
│ │ ├── maven-metadata.xml.md5
│ │ ├── maven-metadata.xml.sha1
│ │ ├── maven-metadata.xml.sha256
│ │ └── maven-metadata.xml.sha512
│ └── org
│ └── sample
│ ├── Goodbye
│ │ └── org.sample.Goodbye.gradle.plugin
│ │ ├── 1.0
│ │ │ ├── org.sample.Goodbye.gradle.plugin-1.0.pom
│ │ │ ├── org.sample.Goodbye.gradle.plugin-1.0.pom.md5
│ │ │ ├── org.sample.Goodbye.gradle.plugin-1.0.pom.sha1
│ │ │ ├── org.sample.Goodbye.gradle.plugin-1.0.pom.sha256
│ │ │ └── org.sample.Goodbye.gradle.plugin-1.0.pom.sha512
│ │ ├── maven-metadata.xml
│ │ ├── maven-metadata.xml.md5
│ │ ├── maven-metadata.xml.sha1
│ │ ├── maven-metadata.xml.sha256
│ │ └── maven-metadata.xml.sha512
│ └── Hello
│ └── org.sample.Hello.gradle.plugin
│ ├── 1.0
│ │ ├── org.sample.Hello.gradle.plugin-1.0.pom
│ │ ├── org.sample.Hello.gradle.plugin-1.0.pom.md5
│ │ ├── org.sample.Hello.gradle.plugin-1.0.pom.sha1
│ │ ├── org.sample.Hello.gradle.plugin-1.0.pom.sha256
│ │ └── org.sample.Hello.gradle.plugin-1.0.pom.sha512
│ ├── maven-metadata.xml
│ ├── maven-metadata.xml.md5
│ ├── maven-metadata.xml.sha1
│ ├── maven-metadata.xml.sha256
│ └── maven-metadata.xml.sha512
├── resources
│ └── main
│ └── META-INF
│ └── gradle-plugins
│ ├── org.sample.Goodbye.properties
│ └── org.sample.Hello.properties
├── test-results
│ └── ...
└── tmp
├── ...
78 directories, 71 files
maven-publsh
プラグインがたくさんのファイルを生成しました。しかしstep4までに学んだ知見を踏まえると、こうなるはずと納得できます。
step6 Gradle Plugin Portalにパブリッシュする
自作したカスタムGradleプラグインを Gradle Plugin Portal にパブリッシュして一般公開します。
参照すべきドキュメント
下記のGradle公式ドキュメントを一読のこと。
まず最初に Gradle Plugin Portalに自分専用のアカウントを作ることが必要です。下記の指示に従って済ませましょう。
settings.gradle と build.gradle
step6
ディレクトリを作り、step6/settings.gradle
ファイルと step6/build.gradle
ファイルを作りました。
step6/settings.gradle
rootProject.name = 'step6'
step6/build.gradle
リンク step6/build.gradle を参照のこと。
第1行目の plugins { ... }
に注目してください。step5までは java-gradle-plugin
と maven-publis
プラグインを適用してきましたが、step6からは com.gradle.plugin-publish
プラグインを適用します。
plugins {
id 'groovy'
//id 'java-gradle-plugin'
//id 'maven-publish'
id 'com.gradle.plugin-publish' version '1.1.0'
}
それから gradlePlugin
extension クロージャを記述した。
gradlePlugin {
website = 'https://kazurayam.github.io/PublishingCustomGradlePlugin_StepByStep/'
vcsUrl = 'https://github.com/kazurayam/PublishingCustomGradlePlugin_StepByStep/'
plugins {
MyGreeting {
id = 'io.github.kazurayam.Greetings'
displayName = 'Plugin for living nice to others'
description = 'A plugin that prints hello and goodbye'
tags.set(['testing', 'lesson'])
com.gradle.plugin-publish
プラグインは何をするのか、何をしないのか
いろいろ試したあげく、わたしはcom.gradle.plugin-publish
プラグインを次のように理解しました。
-
わたしが自作したカスタムGradleプラグインを他の人と共有するためには、つまり彼らが自分のビルドの冒頭に
plugins { id "org.sample.Greetings" version "1.0" }}
と記述するだけでわたしのプラグインを利用できるようにするには、カスタムGradleプラグインを Gradle Plugin Portal に上げることが必要だ。そのためにはcom.gradle.plugin-publish
を使わなければならない。このプラグインを使うことが必須条件であり、他の選択肢は無い。 -
com.gradle.plugin-publish
プラグインは自動的にjava-gradle-plugin
プラグインとmaven-publish
プラグインを適用する。だから私はこれら2つのプラグインをbuild.gradleの冒頭のplugins {...}
に書かなくてよい。 -
com.gradle.plugin-publish
プラグインを動かすために開発者が build.gradleファイルの中に記述する必要があるのはgradlePlugin { … }
クロージャーだけである。 -
com.gradle.plugin-publish
プラグインはGradle Plugin Portalがどこにあるのか(URLが何か)をあらかじめ知っている。だからGradle Plugin PortalのURLを伝えるためにpublishing { repository { maven { … }}}
を書くということは無用。 -
com.gradle.plugin-publish
プラグインはバイナリclassのjarファイルとソースのjarファイルとjavadocのjarファイルとそれに関係するメタデータ(POMファイルとか)を自動的に生成する。だから開発者はjarを生成するためにtaskを記述する必要がない。 -
com.gradle.plugin-publish
プラグインがjarファイルの名前を決定する。 -
開発者がjarファイルの名前を自分の好きなように指定したいと思うかもしれないが、それはできない。
-
カスタムGradleプラグインといえども一つのJava/Groovyプログラムであることに変わりはない。だから開発者がみづから build.gradleファイルの中に Jarタスクを記述してバイナリclassのjarファイル、ソースのjarファイル、javadocのjarファイルをカスタムに作ることはできる。さらに
maven-publish
プラグインをapplyして自作のjarを適当なMavenレポジトリに向けてパブリッシュすることはできる。 -
しかしながら、
com.gradle.plugin-publish
プラグインはカスタムに準備されたjarファイルを無視する。だからわたしがカスタムに準備したjarファイルを Gradle Plugin Portal に向けてパブリッシュすることはできない。
ビルドを実行すると --- エラーが発生した
まず step6/build
ディレクトリを空っぽにした。
:~/github/PublishingCustomGradlePlugin_StepByStep/step6 (develop *)
$ gradle clean
BUILD SUCCESSFUL in 895ms
1 actionable task: 1 executed
次に gradle publishToMavenLocal
タスクを実行した
$ gradle publishToMavenLocal
BUILD SUCCESSFUL in 1s
10 actionable tasks: 6 executed, 4 up-to-date
これによって ~/.m2
ディレクトリの下にファイルが作られていることを確認できた。
$ tree ~/.m2/repository/io/github/kazurayam/
/Users/kazuakiurayama/.m2/repository/io/github/kazurayam/
├── Greetings
│ └── io.github.kazurayam.Greetings.gradle.plugin
│ ├── 1.0
│ │ └── io.github.kazurayam.Greetings.gradle.plugin-1.0.pom
│ └── maven-metadata-local.xml
├── step6
│ ├── 1.0
│ │ ├── step6-1.0-javadoc.jar
│ │ ├── step6-1.0-sources.jar
│ │ ├── step6-1.0.jar
│ │ └── step6-1.0.pom
│ └── maven-metadata-local.xml
...
step6-1.0.jar
ファイルが作られている。その中身はどうなっているのだろう?確認してみた。
$ tar -xvf ~/.m2/repository/io/github/kazurayam/step6/1.0/step6-1.0.jar
x META-INF/
x META-INF/MANIFEST.MF
x com/
x com/example/
x com/example/greeting/
x com/example/greeting/GreetingPlugin$_apply_closure2$_closure4.class
x com/example/greeting/GreetingPlugin$_apply_closure2.class
x com/example/greeting/GreetingPlugin$_apply_closure1$_closure3.class
x com/example/greeting/GreetingPlugin.class
x com/example/greeting/GreetingPlugin$_apply_closure1.class
x META-INF/gradle-plugins/
x META-INF/gradle-plugins/io.github.kazurayam.Greetings.properties
大丈夫そうだ。以上で gradle publishPlugins
コマンドを実行して、Gradle Plugin Portalにパブリッシュする準備ができたはず。やってみた。
$ gradle publishPlugins
> Task :publishPlugins FAILED
Publishing plugin io.github.kazurayam.Greetings version 1.0
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':publishPlugins'.
> Failed to post to server.
Server responded with:
Plugin id 'io.github.kazurayam.Greetings' and group id 'com.example' must use same top level namespace, like 'com.example' or 'io.github.kazurayam'
おっと!エラーが発生した。何がまずいのだろうか?
わたしはGradleビルドの group id として com.example
という値を設定していた。そのいっぽう、plugin id として io.github.kazurayam.XXXX
という値を設定していた。ここに名前空間の不統一があった。それでもローカルに一時的に作ったMavenレポジトリに上げるときにこの不統一は問題にならなかった。ところが Gradle Plugin Portal は group id と plugin id の名前空間の上位を統一することを要求する。それを上記のメッセージが教えている。
つまり Gradle Plugin Portal は下記のいづれかのやり方に修正せよとわたしに要求している。
-
group id =
io.github.kazurayam
, plugin id =io.github.kazurayam.Greeings
-
group id =
com.example
, plugin id =com.example.Greetings
開発者がこの規則に従うことをGradle Plugin Portalが要求する。だから com.gradle.plugin-publish
プラグインはそのようなチェックを実施したところわたしの試みが引っかかってしまった。
よろしい。次のstep7で対応しよう。
step7 group id と plugin id を整える
前のstep6で述べたように、カスタムGradleプラグインをGradle Plugin Portalに向けてパブリッシュしようと試みたところエラーが発生した。Gradle Plugin Portalサイトは group id
と plugin id
の名前空間の上位層が食い違っているとダメだよという。ならば指図に従うまでのこと。やってみよう。
settings.gradle と build.gradle
step7/settings.gradle
rootProject.name = 'step7'
step7/build.gradle
リンク step7/build.gradle を参照のこと。
build.gradle
ファイルの中で io.github.kazurayam
という名前空間を group id と plugin id の両方に設定した。すなわち
group = 'io.github.kazurayam'
version = '1.1'
と
plugins {
MyGreeting {
id = 'io.github.kazurayam.Greetings'
というように。
けっきょくどういう名前空間を指定するのが正解なのか
けっきょく"group id"と"plugin id"と実装クラスのpackage名をどのように指定したのか、下記にまとめた。
entity | how I configured in the build.gradle |
---|---|
the build project’s group id |
|
the plugin id |
|
the implementation class |
|
つまりこういうことだ。
- group id と plugin id とは名前空間の上位層を統一しなければならない。
- 実装クラスのパッケージ名はなんでもいい。group idと一致していなくてかまわない。plugin idと不一致でもかまわない。
Gradle Plugin Portalはプラグインの作者が自分に固有の名前を使うことを求める
わたしはstep6までgroup idに'com.example'という値を指定していた。自分のローカルなMavenレポジトリにパブリッシュするだけなら咎められることはなかった。しかしGradle Plugin Portalにパブリッシュしようとして咎められた。そこでgroup idをio.github.kazurayam
に変更した。このgroup idをGradle Plugin Portalは受け付けてくれた。
こう考えることもできる。Gradle Plugin Portalは複数の開発者が共有する公の場所だ。そこでは開発者が各々が管理責任を負う領域を明確にすること、他人の成果物を勝手に書き換えるような無法を行わないこと、それがマナーとなる。だから com.example
などというのっぺらぼうな名前を名乗ることをGradle Plugin Portalは許さない。当然そうあるべきだとおもいます。
ビルドを実行すると何が起きるか
local Maven cacheに向けてパブリッシュしてみた。
$ gradle publishToMavenLocal
BUILD SUCCESSFUL in 3s
10 actionable tasks: 10 executed
そしてlocal Maven Cache の中身を観察した。
$ tree ~/.m2/repository/io/github/kazurayam/
/Users/kazuakiurayama/.m2/repository/io/github/kazurayam/
├── Greetings
│ └── io.github.kazurayam.Greetings.gradle.plugin
│ ├── 1.1
│ │ └── io.github.kazurayam.Greetings.gradle.plugin-1.1.pom
│ └── maven-metadata-local.xml
├── step7
│ ├── 1.1
│ │ ├── step7-1.1-javadoc.jar
│ │ ├── step7-1.1-sources.jar
│ │ ├── step7-1.1.jar
│ │ └── step7-1.1.pom
│ └── maven-metadata-local.xml
...
12 directories, 23 files
同じ中身をGradle Plugin Portalに向けてパブリッシしてみた。
$ gradle publishPlugin
> Task :publishPlugins
Publishing plugin io.github.kazurayam.Greetings version 1.1
Publishing artifact build/libs/step7-1.1.jar
Publishing artifact build/libs/step7-1.1-javadoc.jar
Publishing artifact build/libs/step7-1.1-sources.jar
Publishing artifact build/publications/pluginMaven/pom-default.xml
Activating plugin io.github.kazurayam.Greetings version 1.1
BUILD SUCCESSFUL in 5s
8 actionable tasks: 2 executed, 6 up-to-date
ついに自作のカスタムGradleプラグインを Gradle Plugin Portal に向けてパブリッシュすることに成功した。
パブリッシュの操作をした直後に https://plugins.gradle.org/search?term=io.github.kazurayam にアクセスしてみたが、わたしのプラグインはまだサイト上に現れなかった。なんらかの事情で時間がかかるのかもしれない。待つことしばし、十時間後にもう一度アクセスしたらサイト上に現れた。だからわたしのパブリッシュ操作は成功したと言える。
Gradle Plugin Portalの中に何がある?
Gradle Plugin Portal というサイトは一つの Mavenレポジトリ を内部に持っていて、その中にはいわゆるカスタムGradleプラグインと呼ばれる種類の成果物だけが集められている。MavenレポジトリのURLも公開されていて、ブラウザでそのURLにアクセスすることができる。下記のURLがそれだ。
step8 PluginMavenPublication にユニークな artifactId を指定する必要があった
前のstep7で、カスタムGradleプラグインをGradle Plugin Portalにパブリッシュすることに成功したものの、問題が残っていることに気がついた。
解決すべき問題 --- artifactIdの重複
step7のディレクトリの中でこういう操作をした。
$ gradle publishToMavenLocal
BUILD SUCCESSFUL in 1s
10 actionable tasks: 4 executed, 6 up-to-date
:~/github/PublishingCustomGradlePlugin_StepByStep/step7 (develop *)
$ tree ~/.m2/repository/io/github/kazurayam
/Users/kazuakiurayama/.m2/repository/io/github/kazurayam
├── Greetings
│ └── io.github.kazurayam.Greetings.gradle.plugin
│ ├── 1.0
│ │ └── io.github.kazurayam.Greetings.gradle.plugin-1.0.pom
│ ├── 1.1
│ │ └── io.github.kazurayam.Greetings.gradle.plugin-1.1.pom
│ └── maven-metadata-local.xml
├── step6
│ ├── 1.0
│ │ ├── step6-1.0-javadoc.jar
│ │ ├── step6-1.0-sources.jar
│ │ ├── step6-1.0.jar
│ │ └── step6-1.0.pom
│ └── maven-metadata-local.xml
├── step7
│ ├── 1.0
│ │ ├── step7-1.0-javadoc.jar
│ │ ├── step7-1.0-sources.jar
│ │ ├── step7-1.0.jar
│ │ └── step7-1.0.pom
│ ├── 1.1
│ │ ├── step7-1.1-javadoc.jar
│ │ ├── step7-1.1-sources.jar
│ │ ├── step7-1.1.jar
│ │ └── step7-1.1.pom
│ └── maven-metadata-local.xml
12 directories, 23 files
私は step6, step7 というシンボルが気になった。こいつらは元々 PublisingCustomGradlePlugin_StepByStep
レポジトリの中のサブディレクトリの名前であった。サブディレクトリの名前としては好ましいシンボルだ。ところが同じシンボルがMavenレポジトリの中に配置されると、step6だのstep7だのは抽象的すぎてMavenレポジトリに格納されるモノを識別するための情報としてはふさわしくない。
step6だのstep7というシンボルは将来、エラーの原因になりうる。わたしは別の仕事のため別のプロジェクトを作り出すかもしれない。そのために別のレポジトリを作るかもしれない。そのレポジトリの名前が仮に "MyGreaterGradlePlugin" だとしよう。私は MyGreaterGradlePlugin レポジトリの中に "step6", "step7" というサブフォルダを設けるかもしれない。そして MyGreaterGradlePluginレポジトリのコードからGradle Plugin Portalにコードをpublishするだろう。すると同じartifactId (step6, step7, )をGradle Plugin Portalに送り込むことになる。ここで名前の重複が発生する。publish操作は成功するが結果として中身がぐちゃぐちゃになるか、あるいはpublish操作の段階でエラーが発生して失敗するか。どちらにせよ、artifactIdの重複を回避しなければならない。それが唯一の解決方法だ。
どうすればいい? --- step6, step7 のようなディレクトリ名に対応した名前ではなく、GreetingsImpl
のような名前、プラグインの氏素性をちゃんと表現するシンボルを使ってpublicationsを生成したい。どうすればできる?
答えはある。次のstep8で試そう。
step8 プラグインの氏素性を表すartifactIdを指定する
settings.gradle と build.gradle
step8/settings.gradle
// step8
rootProject.name = 'GreetingsImpl'
はい、ここが肝心です。 settings.gradle
ファイルにおいて rootProject.name
プロパティに GreetingsImpl
という値を指定した。ディレクトリ名に由来する step7
とかいうシンボルではなく、カスタムGradleプラグインの氏素性を表す固有名としてふさわしいシンボルを指定した。com.gradle.plugin-publish
プラグインはカスタムGradleプラグインの実行クラスのjarファイルのbas nameとして rootProject.name
の値を設定するから、Mavenレポジトリに現れる名前が step7
ではなくて GreetingsImpl
に入れ替わるはずだ。
step8/build.gradle
リンク step8/build.gradle を参照のこと。
step8/build.gradle
ファイルは step7/build.gradle
と全く同じだ。
ビルドを動かすと何が起きるか
最初にlocal Maven cacheを空っぽにした。
$ rm -rf ~/.m2/repository/io/github/kazurayam
それからカスタムGradleプラグインをlocal Maven cacheにパブリッシュした。
$ gradle publishToMavenLocal
BUILD SUCCESSFUL in 3s
10 actionable tasks: 10 executed
local Maven cacheがどうなったか?
$ tree -xvf ~/.m2/repository/io/github/kazurayam/
/Users/kazuakiurayama/.m2/repository/io/github/kazurayam
├── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingsImpl
│ ├── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingsImpl/1.2
│ │ ├── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingImpl/1.2/GreetingsImpl-1.2-javadoc.jar
│ │ ├── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingImpl/1.2/GreetingsImpl-1.2-sources.jar
│ │ ├── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingImpl/1.2/GreetingsImpl-1.2.jar
│ │ └── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingImpl/1.2/GreetingImpl-1.2.pom
│ └── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/GreetingImpl/maven-metadata-local.xml
└── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/Greetings
└── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/Greetings/io.github.kazurayam.Greetings.gradle.plugin
├── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/Greetings/io.github.kazurayam.Greetings.gradle.plugin/1.2
│ └── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/Greetings/io.github.kazurayam.Greetings.gradle.plugin/1.2/io.github.kazurayam.Greetings.gradle.plugin-1.2.pom
└── /Users/kazuakiurayama/.m2/repository/io/github/kazurayam/Greetings/io.github.kazurayam.Greetings.gradle.plugin/maven-metadata-local.xml
6 directories, 7 files
いい感じだ。これならartifactIdが重複するのを避けることができるだろう。
これをGradle Plugin Portalにパブリッシュしよう。
$ gradle publishPlugins
> Task :publishPlugins
Publishing plugin io.github.kazurayam.Greetings version 1.2
Publishing artifact build/libs/greeting-gradle-plugin-1.2.jar
Publishing artifact build/libs/greeting-gradle-plugin-1.2-sources.jar
Publishing artifact build/libs/greeting-gradle-plugin-1.2-javadoc.jar
Publishing artifact build/publications/pluginMaven/pom-default.xml
Activating plugin io.github.kazurayam.Greetings version 1.2
BUILD SUCCESSFUL in 12s
8 actionable tasks: 5 executed, 3 up-to-date
やっとできた。
結論
次の2箇所に書いてあることが本記事のユニークなところです。Gradle公式ドキュメントには書いてない。ご一読あれ。
おしまい。