1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

カスタムGradleプラグインをGradle Plugin Portalに公開する手順を詳しく説明します

Last updated at Posted at 2023-03-29

わたしが書いた 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-pluginmaven-publishcom.gradle.plugin-publish プラグインが <projectDir>/build ディレクトリの中にどんな物(ディレクトリとファイル)を生成するのかを検証した。さまざまのコマンドを実行する都度、<projectDir>/buildディレクトリの中に生成されたファイルのツリーを眺め、ファイルをエディタで開き内容を理解しようとした。

段階的な分析の成果としてわたしが理解したことをこの記事で説明します。Gradleの公式ドキュメントの森の中を彷徨っている誰かの助けになればいいなと願って。

step1 はじめにGroovyコードありき

step1という名前のGradleプロジェクトを作り、その中にカスタムGradleプラグインとなるはずのGroovyコードを書きました。

settings.gradle と build.gradle

レポジトリ のルートディレクトリの下に step1 ディレクトリを作り、Gradleプロジェクトとして必須であるファイル群を作った。 step1/settings.gradlestep1/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コードをコンパイルしようとした。しかし失敗した。

1 1.GradleApiNotAccessible

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-pluginstep2/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テストを実行して成功した。

2.1 success compile and test

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" って何だ? --- 公式ドキュメントに説明がちょっと書いてあります。

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 the sketch repository.

  • publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.

ほかのタスクはマクロではなくてそれぞれ完結した役目を持つタスクだ。

  • 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.

Publications とは何か

タスク名を観察すると、その中で二つのシンボルが使われているのがわかる。すなわち MyGreetingPluginMarkerMavenpluginMaven だ。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-publishmaven-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.Helloorg.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-pluginmaven-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プラグインを次のように理解しました。

  1. わたしが自作したカスタムGradleプラグインを他の人と共有するためには、つまり彼らが自分のビルドの冒頭に plugins { id "org.sample.Greetings" version "1.0" }} と記述するだけでわたしのプラグインを利用できるようにするには、カスタムGradleプラグインを Gradle Plugin Portal に上げることが必要だ。そのためには com.gradle.plugin-publish を使わなければならない。このプラグインを使うことが必須条件であり、他の選択肢は無い。

  2. com.gradle.plugin-publish プラグインは自動的に java-gradle-plugin プラグインと maven-publish プラグインを適用する。だから私はこれら2つのプラグインをbuild.gradleの冒頭の plugins {...} に書かなくてよい。

  3. com.gradle.plugin-publish プラグインを動かすために開発者が build.gradleファイルの中に記述する必要があるのは gradlePlugin { …​ } クロージャーだけである。

  4. com.gradle.plugin-publish プラグインはGradle Plugin Portalがどこにあるのか(URLが何か)をあらかじめ知っている。だからGradle Plugin PortalのURLを伝えるために publishing { repository { maven { …​ }}} を書くということは無用。

  5. com.gradle.plugin-publish プラグインはバイナリclassのjarファイルとソースのjarファイルとjavadocのjarファイルとそれに関係するメタデータ(POMファイルとか)を自動的に生成する。だから開発者はjarを生成するためにtaskを記述する必要がない。

  6. com.gradle.plugin-publish プラグインがjarファイルの名前を決定する。

  7. 開発者がjarファイルの名前を自分の好きなように指定したいと思うかもしれないが、それはできない。

  8. カスタムGradleプラグインといえども一つのJava/Groovyプログラムであることに変わりはない。だから開発者がみづから build.gradleファイルの中に Jarタスクを記述してバイナリclassのjarファイル、ソースのjarファイル、javadocのjarファイルをカスタムに作ることはできる。さらにmaven-publish プラグインをapplyして自作のjarを適当なMavenレポジトリに向けてパブリッシュすることはできる。

  9. しかしながら、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 は下記のいづれかのやり方に修正せよとわたしに要求している。

  1. group id = io.github.kazurayam, plugin id = io.github.kazurayam.Greeings

  2. 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 idplugin 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名をどのように指定したのか、下記にまとめた。

Namespace rule
entity how I configured in the build.gradle

the build project’s group id

group="io.github.kazurayam"

the plugin id

gradlePlugin { plugins { MyGreeting { id = 'io.github.kazurayam.Greetings'

the implementation class

com.example.greeting.GreetingPlugin

つまりこういうことだ。

  1. group id と plugin id とは名前空間の上位層を統一しなければならない。
  2. 実装クラスのパッケージ名はなんでもいい。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

やっとできた。

published to Gradle Plugin Portal

結論

次の2箇所に書いてあることが本記事のユニークなところです。Gradle公式ドキュメントには書いてない。ご一読あれ。

おしまい。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?