Gradle のプラグインの作り方メモ

ビルドスクリプトに直接定義する方法もあるそうだけど、今回はスタンドアロンで動かせるやつを勉強する。

# スタンドアロンのプラグインを作る
## 実装する
```text:フォルダ構成
|-build.gradle
`-src/main/
  |-groovy/
  |  `-sample/gradle/plugin/
  |     `-MyGradlePlugin.groovy
  `-resources/
     `-META-INF/gradle-plugins/
        `-com.github.opengl-8080.hello.properties
```

プラグインは、最終的に Java バイトコードになるなら言語は問わない（Java, Groovy, Scala など）。
ただ、 Java 8 の API とか使うと実行環境によってはエラーになるかもしれないから、あんまり最新の API は使わないようにしたほうがいいのかもしれない。

今回は Groovy で書くことにする。

```groovy:build.gradle
apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

jar.baseName = 'gradle-my-plugin'
```

- Groovy のプロジェクトとして作成する。
- 依存関係には、 `gradleApi()` と `localGroovy()` を指定している。
    - `gradleApi()` は、プラグイン作成に必要になる Gradle の API を参照するための依存関係。
    - `localGroovy()` はビルドに使用している Gradle に同梱されている Groovy を使うという意味。
    - なので、使ってる Gradle のバージョンによっては Groovy のバージョンも異なってくる。
    - Groovy のバージョンを限定したい場合は、ちゃんと Groovy のバージョンを明示した依存関係の宣言にしたほうがいいと思われる（今回はとりあえず `localGroovy()` で）。

```groovy:MyGradlePlugin.groovy
package sample.gradle.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

class MyGradlePlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.task('hello') << {
            println 'Hello My Gradle Plugin!!'
        }
    }
}
```

- プラグインは、 `Plugin` インターフェースを実装することで作成できる。
- `apply()` メソッドをオーバーライドして、そこでプラグインの処理を実装する。
- 引数には `Project` のインスタンスが渡されるので、 `task()` メソッドなどでプロジェクトにタスクを追加したりできる。
- ここでは、 `hello` というタスクを追加している。

```properties:com.github.opengl-BOBO.hello.properties
implementation-class=sample.gradle.plugin.MyGradlePlugin
```

- クラスパス以下の `META-INF/gradle-plugins` の下にプロパティファイルを配置する。
- ここに、プラグインのクラスがどれなのかについての設定を記述する。
- プロパティのキーは `implementation-class` で、値にはプラグインクラスの FQCN を指定する。
- ファイル名は、そのままプラグインの名前になる。
- なので、他のプラグインとバッティングしない名前をつけるようにする。
- たいていの場合は GitHub にソースを公開すると思うので、 GitHub のアカウント名を利用して `com.github.<アカウント名>.<プラグイン名>.properties` という名前にすることが多いっぽい。

## ビルドする
```text:
> gradle jar
```

`build/libs` の下に `gradle-my-plugin.jar` が出力される。

## 利用する
作成した `gradle-my-plugin.jar` を適当なフォルダに移動して、同じ場所に `build.gradle` を作る。

```groovy:build.gradle
buildscript {
    dependencies {
        classpath files('gradle-my-plugin.jar')
    }
}

apply plugin: 'com.github.opengl-BOBO.hello'
```

- `buildscript` の `dependencies` で、作成した jar ファイルを読みこむ。
- そして、先ほどのプロパティファイルの名前でプラグインの読み込みを宣言する。

```text:実行結果
> gradle hello
:hello
Hello My Gradle Plugin!!

BUILD SUCCESSFUL

Total time: 2.309 secs
```

動いた！

## 入力値を受け取る
### 実装修正
```groovy:MyGradlePluginConfiguration.groovy
package sample.gradle.plugin

class MyGradlePluginConfiguration {
    String value
}
```

```groovy:MyGradlePlugin.groovy
package sample.gradle.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

class MyGradlePlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        // ★extensions.create() で hello という設定用の API を定義する。
        //   MyGradlePluginConfiguration の Class オブジェクトを一緒に渡す。
        project.extensions.create('hello', MyGradlePluginConfiguration);
        
        project.task('hello') << {
            println "Hello My Gradle Plugin!! value = ${project.hello.value}" // ★設定値を参照する
        }
    }
}
```

### 利用する側のビルドスクリプト
```groovy:build.gradle
buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath files('gradle-my-plugin-1.0.1.jar')
  }
}

apply plugin: "com.github.opengl-BOBO.hello"

// ★extensitons.create() で追加した hello を使って、設定値を渡せるようになる
hello {
    value = 'doya' // ★MyGradlePluginConfiguration クラスに宣言した value フィールド
}
```

### 実行する
```text:
> gradle hello
:hello
Hello My Gradle Plugin!! value = doya

BUILD SUCCESSFUL

Total time: 2.3 secs
```

### 説明
- ビルドスクリプトから、プラグインに対して入力値を渡したい場合の実装方法。
- プラグインの実装で、 `Project` インスタンスの `extensions.create()` というメソッドを使用する。
    - 第一引数に、ビルドスクリプトから参照するときの名前（`hello`）を指定し、第二引数で値の入れ物となるクラスの `Class` オブジェクトを渡す。
- これで、ビルドスクリプトから `hello` という名前でプラグインに設定値を渡せるようになる。

# プラグインを公開する
Gradle のプラグインは、 [Gradle Plugin Portal](https://plugins.gradle.org/) で簡単に公開できるようになっている。

ただし、実際に公開してみる前に [最後のやつ](#プラグインを削除する) を読んでおいてください。

## サインアップ（アカウント作成）
まずは、アカウントを作成する。
すでに GitHub のアカウントがあれば、 [こちら](https://login.gradle.org/user/login) から簡単にサインアップできる。

## API キーを作成する
サインアップが完了したら、ログインしてユーザー画面にある「API Keys」タブをクリックする。

「Generate Publishing Key」をクリックする。

API キーが作成されるので、 `<Gradle のホームディレクトリ>/.gradle/gradle.properties` に API キーの情報を追記する。
（`<Gradle のホームディレクトリ>` は、何も指定していなければユーザーのホームディレクトリになる。環境変数 `GRADLE_USER_HOME` を明示している場合は、そっち）

```properties:gradle.properties
gradle.publish.key=xxxxxxxxxxxxxxxxxxxxxxxxxxx
gradle.publish.secret=xxxxxxxxxxxxxxxxxxxxxxxxxxx
```

## Plugin Publishing プラグインを使って公開する
自作のプラグインを Gradle Plugin Portal に公開するには、 **Plugin Publishing** という Gradle のプラグインを使う（メタい）。
このプラグイン自体も Gradle Plugin Protal に公開されている。

先ほどのサンプルプラグインのビルドスクリプトを修正する。

```groovy:build.gradle
// ★まずは Plugin Publishing プラグインを読み込む
buildscript {
    repositories {
        maven {
            url 'https://plugins.gradle.org/m2/'
        }
    }
    dependencies {
        classpath 'com.gradle.publish:plugin-publish-plugin:0.9.2'
    }
}

apply plugin: 'com.gradle.plugin-publish'
apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

// ★プラグインのバージョンを設定
version = '1.0.1'
group = 'com.github.opengl-8080'

// ★プラグインの詳細情報を設定する
pluginBundle {
    // ★web サイトの URL
    website = 'https://github.com/opengl-8080/sample-gradle-custom-plugin'
    // ★コード管理をしているサイトの URL
    vcsUrl = 'https://github.com/opengl-8080/sample-gradle-custom-plugin'
    // ★説明
    description = 'Gradle Custom Plugin - Hello World'
    // ★タグ
    tags = ['sample', 'hello world']

    plugins {
        // ★プラグインごとの定義
        hello {
            // ★ID（apply plugin: 'xxxxxxx' で指定するやつ）
            id = 'com.github.opengl-BOBO.hello'
            // ★プラグインの名前
            displayName = 'Sample Gradle Custom Plugin'
        }
    }
}

jar.baseName = 'gradle-my-plugin'
```

こんな感じでプラグインについての設定を記述する。

できたら、 `publishPlugins` タスクを実行する。

```text:
> gradle publishPlugins
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources
:classes
:jar
:groovydoc
:publishPluginGroovyDocsJar
:publishPluginJar
:javadoc UP-TO-DATE
:publishPluginJavaDocsJar UP-TO-DATE
:publishPlugins
Publishing plugin com.github.opengl-BOBO.hello version 1.0.1
Publishing artifact build/libs/gradle-my-plugin-1.0.1.jar
Publishing artifact build/libs/my-plugin-1.0.1-sources.jar
Publishing artifact build/libs/my-plugin-1.0.1-javadoc.jar
Publishing artifact build/libs/my-plugin-1.0.1-groovydoc.jar
Publishing artifact build/publish-generated-resources/pom.xml
Activating plugin com.github.opengl-BOBO.hello version 1.0.1

BUILD SUCCESSFUL

Total time: 9.367 secs
```

公開されたっぽい。

`https://plugins.gradle.org/plugin/<プラグインのID>` にアクセスすると、公開されたプラグインのページが見れる。

公開されたページに `build.gradle` の設定方法が記載されているので、それをコピペしてローカルで動かしてみる。

```groovy:build.gradle
buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.com.github.opengl-8080:my-plugin:1.0.1"
  }
}

apply plugin: "com.github.opengl-BOBO.hello"
```

```text:実行
> gradle hello
Download https://plugins.gradle.org/m2/gradle/plugin/com/github/opengl-8080/my-plugin/1.0.1/my-plugin-1.0.1.pom
Download https://plugins.gradle.org/m2/gradle/plugin/com/github/opengl-8080/my-plugin/1.0.1/my-plugin-1.0.1.jar
:hello
Hello My Gradle Plugin!!

BUILD SUCCESSFUL

Total time: 5.622 secs
```

動いた！

## プラグインを更新する
公開しているプラグイン更新するときは、 `version` を変更して再度 `publishPlugins` タスクを実行すればいい。

既に同じバージョンが公開済みの場合はエラーになる。

## プラグインを削除する
[How to delete a release from https://plugins.gradle.org/? - Help/Discuss / Plugin Portal - Gradle Forums](https://discuss.gradle.org/t/how-to-delete-a-release-from-https-plugins-gradle-org/10620)

> bioinfornatics [2015/07/16]
>   I would like to remove a release from https://plugins.gradle.org/
> 
> arve_knudsen [2015/07/16]
>   Sorry, there is currently **no support** for deleting plugin releases.

えっ

　
　
　
　
　
　
　
えっ

# 参考
- [第59章 カスタムプラグインの作成](http://gradle.monochromeroad.com/docs/userguide/custom_plugins.html)
- [Gradle - How do I get my plugin into the portal?](https://plugins.gradle.org/docs/submit)