mike、mikeなるままに…

プログラムに関してぬるま湯のような記事を書きます

Gradleのmaven-publishプラグインでライブラリーを発行する方法 - 1

こんにちわ、みけです。

ここ数日、gradleのmaven-publishプラグインにはまっていたので、

そのメモです。

なお、例によって長い記事なので、完成されたbuild.gradleだけ見たい方は、いっちばん下に行って下さい。

ただし、完成されたbuild.gradle読んでも理解できないと思いますけどね。

maven-publishプラグインについて

以下のとおりにメモしていきます。

  • maven-publishプラグインの基礎
  • javadoc、sourcesを発行する
  • 複数回、成果物を発行する
  • pomを変更する
  • PGP署名ファイルの発行
  • 課題

maven-publishプラグインの基礎

maven-publishプラグインは任意のファイルを

任意のmavenレポジトリーにアップロードすることができるプラグインです。

次のようなビルドスクリプトでは、以下のようなartifactを発行することができます。

  • sample-project-1.0.jar
  • sample-project-1.0.pom
  • sample-project-1.0-doc.html
build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apply plugin : 'java'
apply plugin : 'maven-publish'
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// publishing description
publishing {
    publications {
        sample(MavenPublication) {
            from components.java
            artifact ('document.html') {
                classifier = 'doc'
                extension  = 'html'
            }
        }
    }
    repositories {
        maven {
            url 'file://Users/mike/maven-sample-repo'
        }
    }
}

また、これらに付随して、それぞれのファイルのmd5ファイルとsha1ファイルも作成されます。

実際に実行してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ gradle clean test publish
:clean UP-TO-DATE
:compileJava
:processResources UP-TO-DATE
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:generatePomFileForSamplePublication
:jar
:publishSamplePublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/maven-sample-repo
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-doc.html to repository remote at file:/Users/mike/maven-sample-repo
Transferring 0K from remote
Uploaded 0K
:publish

BUILD SUCCESSFUL

Total time: 18.255 secs

pomファイルが発行されたのかどうかよくわかりませんが、

実際に発行されたディレクトリーを見てみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
$ cd /Users/mike/maven-sample-repo/org/mikeneck/sample
$ ls
sample-project
$ cd sample-project/
$ ls
1.0    maven-metadata.xml    maven-metadata.xml.md5    maven-metadata.xml.sha1
$ cd 1.0/
$ ls
sample-project-1.0-doc.html       sample-project-1.0.jar      sample-project-1.0.pom
sample-project-1.0-doc.html.md5  sample-project-1.0.jar.md5  sample-project-1.0.pom.md5
sample-project-1.0-doc.html.sha1 sample-project-1.0.jar.sha1 sample-project-1.0.pom.sha1

ということでコマンドを実行したあとの標準出力にはpomについての記述はありませんが、

ちゃんと発行されています。

規約

maven-publishプラグインでは以下のようなルールがあります。

  • 基本的なartifact名はproject名(ディレクトリの名前) + version番号
  • classifierに指定された文字列は上記のファイル名の最後に付与される
  • extensionで指定された文字列は拡張子として付与される
  • classifier + extensionでの一意性のチェックが行われる

javadoc、sourcesを発行する

では、先ほどのサンプルに、ソースとjavadocを付与してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
apply plugin : 'java'
apply plugin : 'maven-publish'
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
}
// zip javadocs
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
}
// publishing description
publishing {
    publications {
        sample(MavenPublication) {
            from components.java
            artifact ('document.html') {
                classifier = 'doc'
                extension  = 'html'
            }
            artifact sourceJar {
                classifier = 'sources'
                extension  = 'jar'
            }
            artifact javadocJar {
                classifier = 'javadoc'
                extension  = 'jar'
            }
        }
    }
    repositories {
        maven {
            url 'file://Users/mike/maven-sample-repo'
        }
    }
}

DSLによれば、メソッドartifactにはtaskを引数にとることができ、指定したtaskの成果物を発行することができます。

上記の例では、sourceJarタスクによってjarファイルに固められたソースと、

javadocJarタスクに寄ってjarファイルに固められたjavadocが、

発行されるようになります。

では、実行してみましょう。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ gradle clean test publish
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:generatePomFileForSamplePublication
:jar
:javadoc
:javadocJar
:sourceJar
:publishSamplePublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-doc.html to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 33K from remote
Uploaded 33K
:publish

BUILD SUCCESSFUL

Total time: 25.84 secs

上記の標準出力からソースとjavadocが出力されていることがわかります。

実際に出力されたファイルを確認してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ cd /Users/mike/maven-sample-repo/org/mikeneck/sample/sample-project/1.0
$ ls -la
total 184
drwxr-xr-x  17 mike  mike   578B  6 19 17:40 .
drwxr-xr-x   6 mike  mike   204B  6 19 17:05 ..
-rw-r--r--   1 mike  mike   346B  6 19 17:40 sample-project-1.0-doc.html
-rw-r--r--   1 mike  mike    32B  6 19 17:40 sample-project-1.0-doc.html.md5
-rw-r--r--   1 mike  mike    40B  6 19 17:40 sample-project-1.0-doc.html.sha1
-rw-r--r--   1 mike  mike    33K  6 19 17:40 sample-project-1.0-javadoc.jar
-rw-r--r--   1 mike  mike    32B  6 19 17:40 sample-project-1.0-javadoc.jar.md5
-rw-r--r--   1 mike  mike    40B  6 19 17:40 sample-project-1.0-javadoc.jar.sha1
-rw-r--r--   1 mike  mike   927B  6 19 17:40 sample-project-1.0-sources.jar
-rw-r--r--   1 mike  mike    32B  6 19 17:40 sample-project-1.0-sources.jar.md5
-rw-r--r--   1 mike  mike    40B  6 19 17:40 sample-project-1.0-sources.jar.sha1
-rw-r--r--   1 mike  mike   1.1K  6 19 17:40 sample-project-1.0.jar
-rw-r--r--   1 mike  mike    32B  6 19 17:40 sample-project-1.0.jar.md5
-rw-r--r--   1 mike  mike    40B  6 19 17:40 sample-project-1.0.jar.sha1
-rw-r--r--   1 mike  mike   404B  6 19 17:40 sample-project-1.0.pom
-rw-r--r--   1 mike  mike    32B  6 19 17:40 sample-project-1.0.pom.md5
-rw-r--r--   1 mike  mike    40B  6 19 17:40 sample-project-1.0.pom.sha1

複数回、成果物を発行する

これまでの例ではsampleという発行タスクにいろいろなものを詰め込んでいました。

たとえば、javadocだけとか、sourcesファイルだけとか発行したい場合、

成果物の発行タスクを切り分けたいような場面があるかと思います。

その場合、publicationsの下の記述を変えることで、成果物の発行タスクを分けることができます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apply plugin : 'java'
apply plugin : 'maven-publish'
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
}
// zip javadocs
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
}
// publishing description
publishing {
    publications {
        // only java archives
        sample(MavenPublication) {
            from components.java
        }
        // publish documents
        documents(MavenPublication) {
            artifact ('document.html') {
                classifier = 'doc'
                extension  = 'html'
            }
            artifact sourceJar {
                classifier = 'sources'
                extension  = 'jar'
            }
            artifact javadocJar {
                classifier = 'javadoc'
                extension  = 'jar'
            }
        }
    }
    repositories {
        maven {
            url 'file://Users/mike/maven-sample-repo'
        }
    }
}

これで、メインのjarを発行するタスクと、

ドキュメント類を発行するタスクを切り分けることができました。

実際、タスクにはどのようなものがあるか確認します。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.

…中略…

Publishing tasks
----------------
publish - Publishes all publications produced by this project.
publishDocumentsPublicationToMavenLocal - Publishes Maven publication 'documents' to the local Maven repository.
publishDocumentsPublicationToMavenRepository - Publishes Maven publication 'documents' to Maven repository 'maven'.
publishSamplePublicationToMavenLocal - Publishes Maven publication 'sample' to the local Maven repository.
publishSamplePublicationToMavenRepository - Publishes Maven publication 'sample' to Maven repository 'maven'.
publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.

…中略…

BUILD SUCCESSFUL

Total time: 21.853 secs

Publishing tasksには次のようなエントリーが入っています。

  • publish すべてを指定したレポジトリーに発行する
  • publicDocumentsPublication… documentsで指定したアーカイブを発行します。
  • publishSamplePublication… sampleで指定したアーカイブを発行します。
  • publishToMavenLocal すべてをmaven localレポジトリーに発行します。

という形で、publishing/publicationsで複数のアーカイブ発行を指定することで、タスクが生成されます。

さて、ここでは、publishタスクを実行してみたいと思います。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ gradle clean test publish
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:generatePomFileForDocumentsPublication
:javadoc
:javadocJar
:sourceJar
:publishDocumentsPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.pom to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-doc.html to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 33K from remote
Uploaded 33K
:generatePomFileForSamplePublication
:jar
:publishSamplePublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K
:publish

BUILD SUCCESSFUL

Total time: 12.33 secs

標準出力を見るとわかりますが、アルファベット順にタスクが実行されます。

そして、もう一つ気になるところがありますね。

各タスクの前に、generatePomFileFor[タスク名]というタスクが実行されています。

これらが発行するpomは何かを調べてみます。

まず、すこしスクリプトを変更します。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
apply plugin : 'java'
apply plugin : 'maven-publish'
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// repositories
repositories {
    mavenCentral ()
}
// dependencies
dependencies {
    compile 'org.jsoup:jsoup:1.6.3'
    testCompile 'junit:junit:4.11'
}
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
}
// zip javadocs
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
}
// publishing description
publishing {
    publications {
        sample(MavenPublication) {
            from components.java
        }
        documents(MavenPublication) {
            artifact ('document.html') {
                classifier = 'doc'
                extension  = 'html'
            }
            artifact sourceJar {
                classifier = 'sources'
                extension  = 'jar'
            }
            artifact javadocJar {
                classifier = 'javadoc'
                extension  = 'jar'
            }
        }
    }
    repositories {
        maven {
            url 'file://Users/mike/maven-sample-repo'
        }
    }
}

dependenciesを追加しました。

この状態で、sampleの方を実行してみます。

なお、このタスクの実行において、タスクの指定に省略名を使用しています。

gradleではpublishToMavenRepositoryのような単語の頭文字が大文字になっているタスクを

頭文字だけを選択してpTMRのように省略することができます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gradle clean pSPTMR
:clean
:generatePomFileForSamplePublication
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:publishSamplePublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K

BUILD SUCCESSFUL

Total time: 2.498 secs

さて、この結果出力されたpomは次のようになります。

sample-project-1.0.pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>org.mikeneck.sample</groupId>
  <artifactId>sample-project</artifactId>
  <version>1.0</version>
  <dependencies>
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.6.3</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

一方、documentの方を実行してみます。

sample-project-1.0.pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ gradle clean pDPTMR
:clean
:generatePomFileForDocumentsPublication
:compileJava
:processResources UP-TO-DATE
:classes
:javadoc
:javadocJar
:sourceJar
:publishDocumentsPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.pom to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-doc.html to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 33K from remote
Uploaded 33K

BUILD SUCCESSFUL

Total time: 4.961 secs

この結果出力されたpomは次のとおり、<dependencies></dependencies>の部分の記述がなくなります。

sample-project-1.0.pom
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>org.mikeneck.sample</groupId>
  <artifactId>sample-project</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
</project>

この違いは、前者のタスクにおいてdependenciesの情報を利用するのに対して、

後者はdependenciesを使わないことにあると勝手に理解しています。

したがって、複数回にわたって成果物を発行する場合は、

pomの生成について気をつけなければなりません。

pomを変更する

前回のポストで記述した通り、maven centralに登録するライブラリーについては、

発行するpomにいくつか追加情報を与えなければならない場合があります。

また、Jettyのservletを用いる場合は、

jetty-orbitという存在しないartifactを避けるために、

直接dependencyを書けない場合などがあります。

そのような場合に、pomを書き換える必要が生じます。

publicationコンテナのpomオブジェクトを用いる

maven-publishプラグインではpublicationコンテナにて

pomオブジェクトを介して発行されるpomにアクセスすることができます。

先ほどのビルドスクリプトにpomを生成するタスクを追加します。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
apply plugin : 'java'
apply plugin : 'maven-publish'
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// repositories
repositories {
    mavenCentral ()
}
// dependencies
dependencies {
    compile 'org.jsoup:jsoup:1.6.3'
    testCompile 'junit:junit:4.11'
}
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
}
// zip javadocs
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
}
// publishing description
publishing {
    publications {
        sample(MavenPublication) {
            from components.java
        }
        documents(MavenPublication) {
            artifact ('document.html') {
                classifier = 'doc'
                extension  = 'html'
            }
            artifact sourceJar {
                classifier = 'sources'
                extension  = 'jar'
            }
            artifact javadocJar {
                classifier = 'javadoc'
                extension  = 'jar'
            }
        }
        // editing pom file with builder style
        pomOnly(MavenPublication) {
            pom.withXml {
                def node = asNode()
                node.children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    dependencies {
                        project.configurations.compile.dependencies.each {dep ->
                            dependency {
                                groupId dep.group
                                artifactId dep.name
                                version dep.version
                            }
                        }
                    }
                    licenses {
                        license {
                            name 'The Apache Software License, Version 2.0'
                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
                            distribution 'repo'
                        }
                    }
                }
            }
        }
    }
    repositories {
        maven {
            url 'file://Users/mike/maven-sample-repo'
        }
    }
}

pomオブジェクトのwithXmlメソッドの引数のClosureは、

org.gradle.api.XmlProviderのメソッドを呼び出すことができます。

そして、asNode()メソッドによりpomファイルをgroovy.util.Nodeの形で取得出来ます。

asNode()で返ってくるNodeの一番トップの部分は<project>要素です。

この要素の子要素を取得し、最後の要素にplusメソッドで要素を追加します。

追加するClosureは、groovy.util.NodeBuilderと同等のDSLによって、

pomに要素を追加していくことができます。

では、このタスクを実行してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
$ gradle clean pPOPTMR
:clean
:generatePomFileForPomOnlyPublication
:publishPomOnlyPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.pom to repository remote at file:/Users/mike/maven-sample-repo/
Transferring 1K from remote
Uploaded 1K

BUILD SUCCESSFUL

Total time: 5.813 secs

発行されたpomファイルは次のようになります。

sample-project-1.0.pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.mikeneck.sample</groupId>
  <artifactId>sample-project</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
  <dependencies>
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.6.3</version>
    </dependency>
  </dependencies>
  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/license/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
</project>

PGP署名ファイルの発行

少し話題が飛びますが、

maven central repositoryにライブラリーを発行する場合、

各アーカイブファイルとpomファイルに対してPGP(Pretty Good Privacy)署名が必要となります。

PGP署名って…?

PGP署名を簡単に説明すると以下のようになります。

  • 配布物を元に、配布元で非公開鍵で暗号化して署名を作る
  • 受け取り側で署名に対して公開鍵で復号化したものと、配布物とを比較する
  • 一致していれば配布物が正しいもの(改ざんされていない)と判定される

というファイルの信頼性を確認する仕組みです。

なお、PGPツールとしては、PGPの仕様RFC4880に準拠した、

GnuPG(Gnu Privacy Guard)を使うのが一般的なようです。

なお、GnuPGの現在のバージョンは2.0です。

また、Javaでの実装ではBCPGが有名です。

また、gradle本題もbcpg-jdk15-1.46を利用しています。

署名タスクを作成する

mavenプラグイン + signプラグインであれば、以下の様な記述で署名を作成することが可能です。

build.gradle
1
2
3
4
5
6
7
['maven', 'signing'].each {apply plugin : it}
artifacts {
    archives jar
}
signing {
    sign configurations.archives
}

しかし、maven-publishプラグインでは、明示的に署名ファイルも取り扱いたいので、

一工夫が必要になります。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// plugins see(1)
['maven-publish', 'signing'].each {apply plugin : it}
// zip sources. see(4)
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
    // add classifier to jar file
    classifier = 'sources'
}
// zip javadocs. see(4)
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
    // add classifier to jar file
    classifier = 'javadoc'
}
// collect artifacts. see(3)
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
}
// sign task. see(2)
task signArchives (type : Sign, dependsOn : [jar, sourceJar, javadocJar]) {
    sign configurations.archives
}
// execute sign task. see(7)
task preparePublication (dependsOn : signArchives)
// extracting signature files with classifier and extension. see(5)
def getSignatureFiles = {
    def allFiles = tasks.signArchives.signatureFiles.collect{it}
    def signedSources = allFiles.find { it.name.contains('-sources') }
    def signedJavadoc = allFiles.find { it.name.contains('-javadoc') }
    def signedJar = (allFiles - [signedSources, signedJavadoc])[0]
    return [
            [archive: signedSources, classifier: 'sources', extension: 'jar.asc'],
            [archive: signedJavadoc, classifier: 'javadoc', extension: 'jar.asc'],
            [archive: signedJar,     classifier: null,      extension: 'jar.asc']
    ]
}
publishing {
    publications {
        // publishing artifacts
        jars(MavenPublication) {
            from components.java
            [
                    [jarTask : tasks.sourceJar,  classifier : 'sources', extension : 'jar'],
                    [jarTask : tasks.javadocJar, classifier : 'javadoc', extension : 'jar']
            ].each {archive ->
                artifact (archive.jarTask) {
                    classifier = archive.classifier
                    extension  = archive.classifier
                }
            }
        }
        // publishing signature files. see(6)
        jarSignatures (MavenPublication) {
            getSignatureFiles().each {signedArchive ->
                artifact (signedArchive.archive) {
                    classifier = signedArchive.classifier
                    extension  = signedArchive.extension
                }
            }
        }
        // publishing pom file
        pom(MavenPublication) {
            pom.withXml {
                def node = asNode()
                node.children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    name 'sample-project'
                    description 'give information of gradle maven-publish plugin'
                    url projectUrl
                    dependencies {
                        project.configurations.compile.dependencies.each {dep ->
                            dependency {
                                groupId dep.group
                                artifactId dep.name
                                version dep.version
                            }
                        }
                    }
                    licenses {
                        license {
                            name 'The Apache Software License, Version 2.0'
                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
                            distribution 'repo'
                        }
                    }
                    scm {
                        url github
                        connection scmUrl
                        developerConnection developerUrl
                    }
                    developers {
                        developer {
                            id 'mike_neck'
                            name 'Shinya Mochida'
                            email 'mike <at> mikeneck.org'
                        }
                    }
                }
            }
        }
    }
    repositories {
        fladDirs "${project.projectDirs}/artifacts"
    }
}

変更点は次のとおりです。

(1) signingプラグインを導入します。

signingプラグインではmavenプラグインと連携して次のように署名を作成することができます。

build.gradle
1
2
3
4
5
6
7
8
artifacts {
    archives jar
    archives javadocJar
    archives sourceJar
}
signing {
    sign configurations.archives
}

しかし、mavenプラグインを使わないので、上記の方法では望みの署名ファイルを取得出来ません。

(2) Signタイプのタスクを作成する

signingプラグインが入っているので、typeSignのタスクを定義することができます。

このタスクを作成しておくと、指定したファイルに対して署名を作成することができます。

なお、このタスクは事前に署名対象のファイルがあることが前提なので、

jarjavadocJarsourceJarタスクに依存しています。

(3) 成果物を一つの変数でアクセスできるようにする

artifacts{}ブロックでは指定したconfigurationに成果物を登録することができます。

次の例ではarchives configurationにjarタスク、javadocJarタスク、sourceJarタスクの成果を

登録します。

build.gradle
1
2
3
4
5
artifacts {
    archives jar
    archives javadocJar
    archives sourceJar
}

これによって、configurations.archivesというプロパティから、

各種タスクの成果物にアクセスできるようになります。

(4) Jarタイプのタスクにclassifierを指定して、成果物のファイル名を修正します。

build.gradle
1
2
3
4
5
6
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
    // add classifier to jar file
    classifier = 'sources'
}

これによって、作成されるsourcesJarのファイル名に-sourcesが含まれるようになります。

(5) 署名ファイルを取り出します

typeSignのタスクのgetSignatureFiles()メソッドは、署名したファイルのリストを返します。

それらをclassifierによって、わけて取り出して、

改めてclassifierextensionを付与します。

(6) 署名ファイルをそれぞれ発行します。

上記の(5)のクロージャーgetSignatureFilesによって、

署名ファイルとclassifierextensionを取得し、

それぞれartifactとして登録、発行します。

(7) 事前に実行しておくタスクをまとめたタスクを追加

署名ファイルを作成するタスクを確実に実行しておくために、

preparePublicationタスクを作成します。

これをpublishタスクの前に実行します。


それではpublishタスクを実行してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$gradle clean pP publish
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:javadoc
:javadocJar
:sourceJar
:signArchives
:preparePublication
:generatePomFileForJarSignaturesPublication
:publishJarSignaturesPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts
Transferring 0K from remote
Uploaded 0K
:generatePomFileForJarsPublication
:publishJarsPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 33K from remote
Uploaded 33K
:generatePomFileForPomPublication
:publishPomPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.pom to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
:publish

BUILD SUCCESSFUL

Total time: 10.894 secs

さて、署名ファイルが作成されているか確認します。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
$ cd /Users/mike/IdeaProjects/sample-project/artifacts/org/mikeneck/sample/sample-project/1.0
$ ls | grep asc
sample-project-1.0-javadoc.jar.asc
sample-project-1.0-javadoc.jar.asc.md5
sample-project-1.0-javadoc.jar.asc.sha1
sample-project-1.0-sources.jar.asc
sample-project-1.0-sources.jar.asc.md5
sample-project-1.0-sources.jar.asc.sha1
sample-project-1.0.jar.asc
sample-project-1.0.jar.asc.md5
sample-project-1.0.jar.asc.sha1

それぞれ署名ファイルが作成されているようです。

では、署名ファイルを検証してみましょう。

build.gradle
1
2
3
4
5
6
7
8
9
$ gpg2 --verify sample-project-1.0-javadoc.jar.asc
gpg: Signature made   6/20 18:05:08 2013 JST using RSA key ID ABC12345
gpg: Good signature from "Shinya Mochida (Groovy/JavaScript Developer in Japan) <mike@mikeneck.org>"
$ gpg2 --verify sample-project-1.0-sources.jar.asc
gpg: Signature made 木  6/20 18:05:08 2013 JST using RSA key ID ABC12345
gpg: Good signature from "Shinya Mochida (Groovy/JavaScript Developer in Japan) <mike@mikeneck.org>"
$ gpg2 --verify sample-project-1.0.jar.asc
gpg: Signature made 木  6/20 18:05:08 2013 JST using RSA key ID ABC12345
gpg: Good signature from "Shinya Mochida (Groovy/JavaScript Developer in Japan) <mike@mikeneck.org>"

ちゃんと署名できていることが確認できました。

課題

さて、jarファイルの署名をすることは出来ました。

pom署名ファイル問題

しかし、残念なことにpomファイルの署名ができていません。

上述のpomファイルを変更するというところで記述した

org.gradle.api.XmlProviderの実装クラスは

org.gradle.api.internal.xml.XmlTransformer.XmlProviderImplです。

そのクラスにはpublic void writeTo(java.io.File file)というメソッドがあります。

そのメソッドを介してpomファイルを出力することが可能です。

したがって、次の手順でpomファイルの署名も発行することが可能ではないかと

考えられます。

  1. pom出力タスク中でpomファイルを書き出し
  2. pom出力タスク中で書きだしたpomファイルの署名をするタスクを実行
  3. pomファイルの署名をするタスクから署名ファイルを取得
  4. 署名ファイルをartifactとして発行

上記の手順を実行するようにビルドスクリプトを書いてみます。

以下、一部抜粋。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// pom file
ext {
    pomFilePath = "${project.projectDir}/tmp/pom.xml"
    pomFile = file(pomFilePath)
}
// task for signing pom
task signPom(type : Sign) {
    sign pomFile
}
// getting a signature of pom
def getPomSignatrure = {
    return project.tasks.signPom.signatureFiles.collect{it}[0]
}
publishing {
    publications {
        // publish pom
        pom(MavenPublication) {
            pom.withXml {
                def node = asNode()
                node.chidren().last() + {
                    dependencies {
                        resolveStrategy = Closure.DELEGATE_FIRST
                        project.configurations.compile.dependencies.each {dep ->
                            dependency {
                                groupId dep.group
                                artifactId dep.name
                                version dep.version
                            }
                        }
                    }
                }
                writeTo(project.ext.pomFile)
                project.tasks.signPom.execute()
                artifact (getPomSignature()) {
                    classifier = null
                    extension  = 'pom.asc'
                }
            }
        }
    }
}

ではpublishタスクを実行してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ gradle --daemon clean pP publish
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:javadoc
:javadocJar
:sourceJar
:signArchives
:preparePublication
:generatePomFileForJarSignaturesPublication
:publishJarSignaturesPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
:generatePomFileForJarsPublication
:publishJarsPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 33K from remote
Uploaded 33K
:generatePomFileForPomPublication
:publishPomPublicationToMavenRepository FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':publishPomPublicationToMavenRepository'.
> Failed to publish publication 'pom' to repository 'maven'
   > Invalid publication 'pom': artifact file does not exist: '/Users/mike/IdeaProjects/sample-project/tmp/pom.xml.asc'

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 5.357 secs

publishPomPublicationToMavenRepositoryタスクで落ちてしまっています。

理由は署名ファイルが見つからないということです。

では、該当のディレクトリーの中身を見てみます。

build.gradle
1
2
$ ls temp
pom.xml

pomファイルだけしか出力されていません。

したがって、署名タスクが実行できてない状態になっているわけです。

実行されたタスクを挙げてみると、このようになっています。

  • :clean
  • :compileJava
  • :processResources UP-TO-DATE
  • :classes
  • :jar
  • :javadoc
  • :javadocJar
  • :sourceJar
  • :signArchives
  • :preparePublication
  • :generatePomFileForJarSignaturesPublication
  • :publishJarSignaturesPublicationToMavenRepository
  • :generatePomFileForJarsPublication
  • :publishJarsPublicationToMavenRepository
  • :generatePomFileForPomPublication
  • :publishPomPublicationToMavenRepository FAILED

よくみてみると、signPomタスクは実行されていません。

というわけで、明示的にsignPomタスクを実行する必要があるわけですが、

(Task#execute()で呼び出さないということ)

Task#dependsOnでsignPomタスクを指定しても、発行できないだけでなく、

実際にはpublishing-publicationのコンテキストではdependsOnが使えません。

先ほどのビルドスクリプトを一部変更してみます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
publishing {
    publications {
        // 中略
        pom(MavenPublication) {
            dependsOn project.tasks.signPom
            pom.withXml {
                // 中略
                writeTo(project.ext.pomFile)
            }
            artifact (getPomSignature()) {
                classifier = null
                extension  = 'pom.asc'
            }
        }
    }
}

このビルドスクリプトをパースさせると次のようなエラーが発生します。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ gradle tasks
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/mike/IdeaProjects/sample-project/build.gradle' line: 90

* What went wrong:
A problem occurred configuring root project 'sample-project'.
> Cannot create a Publication named 'dependsOn' because this container does not support creating elements by name alone. Please specify which subtype of Publication to create. Known subtypes are: MavenPublication

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 13.805 secs

たとえ、これがDSL上問題がなくても、

pomファイルの出力はsignPomタスクの後に実行されるので、

エラーが発生することは予見できます。

現状考えられるpom署名ファイル回避方法

gradleではダイナミックにタスクの定義ができます。

これを利用して、pomファイルがない場合は、

pomファイルの生成を行い、

一時的なファイルに対して発行を行います。

そして、pomファイルが存在する場合には、

署名タスクを実行して、

artifact登録して、maven repositoryへ発行します。

整理すると…

  • maven publishプラグインを二回実行する。
  • pomファイルが存在しない場合は、signPomを実行しない、withXmlwriteToを使ってpomファイルを出力する。
  • pomファイルが存在する場合は、signPomを先に実行しておいて、署名ファイルもartifactとして発行する

ということになります。

ビルドスクリプトを以下に示します(該当部分のみ)。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// dynamic definition of preparePublication
if (pomFile.exists()) {
    task preparePublication (dependsOn : [signArchives, signPom])
} else {
    task preparePublication (dependsOn : signArchives)
}
// publishing if pomFile exists
publishing {
    publications {
        // 中略
        pom(MavenPublication) {
            dependsOn project.tasks.signPom
            pom.withXml {
                // 中略
                if (!project.ext.pomFile.exists()) {
                    writeTo(project.ext.pomFile)
                }
            }
            if (project.ext.pomFile.exists()) {
                artifact (getPomSignature()) {
                    classifier = null
                    extension  = 'pom.asc'
                }
            } else {
                delete(project.ext.pomFile)
            }
        }
    }
}

…で、これは実は失敗しました。

ヒントは、発行されるpomの<packaging></packaging>にあります。

  • jarSignaturesタスクの場合は<packaging>jar.asc</packaging>
  • jarsタスクの場合は<packaging></packaging>要素なし
  • pomタスクの場合はpomFileが存在する場合は<packaging>pom.asc</packaging>、pomFileが存在しない場合は<packaging>要素なし

となります。

pomFileが存在する場合に署名タスクを実行するという戦略をとっていたわけですが、

signPomタスクはpomFileが存在しない場合のpomファイル、

つまり<packaging>要素なしのものを元に署名を行うため、

同時に発行されるpomファイルが<packaging>pom.asc</packaging>を持ってしまうために、

署名ファイルが不適切なものになってしまうという不具合が発生してしまいました。

改めてpomファイルの発行と、署名タスクの戦略を考えなおす

projectのメインのpom出力について

先ほどの<packaging>要素に関しては、

build.gradle
1
2
3
def node = = asNode()
def packagingNode = node.children().find{it.name().localPart.contains('packaging')}
node.remove(packageingNode)

とやっても、発行されるartifactのextensionによって、<packaging>要素が追加されてしまいます。

したがって、pomファイルの修正はjarファイルの発行タスクと同時に実施するのが望ましい形であると言えます。

なぜならば、jarファイルの発行タスクではすべてのextensionjarであるため、

適切なpomファイルが作成されうるためです。

また、これはpomの署名ファイルの発行の元になるために、

ここでpomファイルの書き出しをしておく必要があります。

署名ファイルの生成について

pomファイルが存在している場合にのみ、署名タスクを実行、

署名ファイルアップロードするタスクが実行できるようにします。

タスクの実行順について

前述のとおり、pomファイルは後から実行されるタスクで上書きされていくので、

jarが発行されるタスクが一番最後に実施されるようにする必要があります。

その他の署名ファイルの発行タスクは、その先に実行されるようにします。

以上の戦略に基づいたビルドスクリプト

実行順序などに気をつけてビルドスクリプトを更新しました。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// declaration of plugins
['java', 'maven-publish', 'idea', 'signing'].each {
    apply plugin : it
}
// dynamic properties
ext {
    pomFilePath = "${project.projectDir}/tmp/pom.xml"
    pomFile = file(pomFilePath)
}
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// repository management
repositories {
    mavenCentral ()
}
// dependency management
dependencies {
    compile 'org.jsoup:jsoup:1.6.3'
    testCompile 'junit:junit:4.11'
}
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
    classifier = 'sources'
}
// zip javadoc
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
    classifier = 'javadoc'
}
// configuration for pom signing
configurations {
    pom
}
// extract artifacts for publishing signatures
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
    if (pomFile.exists()) {
        pom pomFile
    }
}
// jar signin
task signArchives (type : Sign, dependsOn : [jar, sourceJar, javadocJar]) {
    sign configurations.archives
}
// getting signature files
def getSignatureFiles = {
    def allFiles = tasks.signArchives.signatureFiles.collect{it}
    def signedSources = allFiles.find { it.name.contains('-sources') }
    def signedJavadoc = allFiles.find { it.name.contains('-javadoc') }
    def signedJar = (allFiles - [signedSources, signedJavadoc])[0]
    return [
            [archive: signedSources, classifier: 'sources', extension: 'jar.asc'],
            [archive: signedJavadoc, classifier: 'javadoc', extension: 'jar.asc'],
            [archive: signedJar,     classifier: null,      extension: 'jar.asc']
    ]
}
// signing pom file
task signPom(type : Sign) {
    sign configurations.pom
}
// prepare publication tasks depends on existence of pom file
if (pomFile.exists()) {
    task preparePublication (dependsOn : [signArchives, signPom])
} else {
    task preparePublication (dependsOn : signArchives)
}
// getting pom signature
def getPomSignature = {
    return project.tasks.signPom.signatureFiles.collect{it}[0]
}
publishing {
    publications {
        // jar publication -- this will be executed last
        mainJar(MavenPublication) {
            from components.java
            [
                    [jarTask : sourceJar,  classifier : 'sources', extension : 'jar'],
                    [jarTask : javadocJar, classifier : 'javadoc', extension : 'jar']
            ].each {archive ->
                artifact (archive.jarTask) {
                    classifier = archive.classifier
                    extension  = archive.extension
                }
            }
            pom.withXml {
                Node node = asNode()
                node.children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    packaging 'jar'
                    licenses {
                        license {
                            name 'The Apache Software License, Version 2.0'
                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
                            distribution 'repo'
                        }
                    }
                }
                if (!project.ext.pomFile.exists()) {
                    writeTo(project.ext.pomFile)
                }
            }
        }
        // publication of jar signatures
        jarSignatures(MavenPublication) {
            getSignatureFiles().each {signature ->
                artifact (signature.archive) {
                    classifier = signature.classifier
                    extension  = signature.extension
                }
            }
        }
        // publication of pom signatures depends on existence of pom file.
        if (project.ext.pomFile.exists()) {
            'gpg-pom'(MavenPublication) {
                artifact (getPomSignature()) {
                    classifier = null
                    extension  = 'pom.asc'
                }
            }
        }
    }
    repositories {
        maven {
            // siwtch repository location by existence of pom file
            if (project.ext.pomFile.exists()) {
                // target repository
                url "file:/${project.projectDir}/artifacts"
            } else {
                // temporary repository
                url "file:/${project.projectDir}/tmp"
            }
        }
    }
}

publish タスクは二度実行します。

一度目はpomファイルを生成するだけが目的です。

二度目はpomの署名も生成して、すべてをmaven repositoryにアップロードします。

一度目のpublishタスク実行結果

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ gradle --daemon clean pP publish
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:javadoc
:javadocJar
:sourceJar
:signArchives
:preparePublication
:generatePomFileForJarSignaturesPublication
:publishJarSignaturesPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/tmp/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/tmp/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/tmp/
Transferring 0K from remote
Uploaded 0K
:generatePomFileForMainJarPublication
:publishMainJarPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/tmp/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/tmp/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/tmp/
Transferring 33K from remote
Uploaded 33K
:publish

BUILD SUCCESSFUL

Total time: 21.149 secs

これは、あくまでpomファイルの生成を目的にしたpublish タスクです。

念の為にpomファイルを確認しておきます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ cat tmp/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.mikeneck.sample</groupId>
  <artifactId>sample-project</artifactId>
  <version>1.0</version>
  <dependencies>
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.6.3</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <packaging>jar</packaging>
  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/license/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
</project>

これが次に実行するpublishタスクにて署名されてmaven repositoryにアップロードされます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ gradle --daemon clean pP publish
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:javadoc
:javadocJar
:sourceJar
:signArchives
:signPom UP-TO-DATE
:preparePublication
:generatePomFileForGpg-pomPublication
:publishGpg-pomPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.pom.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
:generatePomFileForJarSignaturesPublication
:publishJarSignaturesPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar.asc to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 0K from remote
Uploaded 0K
:generatePomFileForMainJarPublication
:publishMainJarPublicationToMavenRepository
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-sources.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 1K from remote
Uploaded 1K
Uploading: org/mikeneck/sample/sample-project/1.0/sample-project-1.0-javadoc.jar to repository remote at file:/Users/mike/IdeaProjects/sample-project/artifacts/
Transferring 33K from remote
Uploaded 33K
:publish

BUILD SUCCESSFUL

Total time: 8.249 secs

pom.ascファイルが無事発行されたようです。

それでは、適切なファイルであるかどうか、検証します。

build.gradle
1
2
3
4
$ cd artifacts/org/mikeneck/sample/sample-project/1.0/
$ gpg2 --verify sample-project-1.0.pom.asc
gpg: Signature made   6/21 04:56:06 2013 JST using RSA key ID ABC12345
gpg: Good signature from "Shinya Mochida (Groovy/JavaScript Developer in Japan) <mike@mikeneck.org>"

適切なファイルであることが検証できたようです。

また、pomファイルの<packaging>の部分がちゃんとjarになっているか

確認しておきます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ cat sample-project-1.0.pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.mikeneck.sample</groupId>
  <artifactId>sample-project</artifactId>
  <version>1.0</version>
  <dependencies>
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.6.3</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <packaging>jar</packaging>
  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/license/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
</project>

ちゃんとjarになっているようなので、

全体的に成功であると判断出来ます。

結論

完成されたbuild.gradleファイルを再掲します。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// declaration of plugins
['java', 'maven-publish', 'idea', 'signing'].each {
    apply plugin : it
}
// dynamic properties
ext {
    pomFilePath = "${project.projectDir}/tmp/pom.xml"
    pomFile = file(pomFilePath)
}
// project information
group = 'org.mikeneck.sample'
version = '1.0'
// repository management
repositories {
    mavenCentral ()
}
// dependency management
dependencies {
    compile 'org.jsoup:jsoup:1.6.3'
    testCompile 'junit:junit:4.11'
}
// zip sources
task sourceJar(type : Jar) {
    from sourceSets.main.allJava
    classifier = 'sources'
}
// zip javadoc
task javadocJar(type : Jar, dependsOn : javadoc) {
    from javadoc.destinationDir
    classifier = 'javadoc'
}
// configuration for pom signing
configurations {
    pom
}
// extract artifacts for publishing signatures
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
    if (pomFile.exists()) {
        pom pomFile
    }
}
// jar signin
task signArchives (type : Sign, dependsOn : [jar, sourceJar, javadocJar]) {
    sign configurations.archives
}
// getting signature files
def getSignatureFiles = {
    def allFiles = tasks.signArchives.signatureFiles.collect{it}
    def signedSources = allFiles.find { it.name.contains('-sources') }
    def signedJavadoc = allFiles.find { it.name.contains('-javadoc') }
    def signedJar = (allFiles - [signedSources, signedJavadoc])[0]
    return [
            [archive: signedSources, classifier: 'sources', extension: 'jar.asc'],
            [archive: signedJavadoc, classifier: 'javadoc', extension: 'jar.asc'],
            [archive: signedJar,     classifier: null,      extension: 'jar.asc']
    ]
}
// signing pom file
task signPom(type : Sign) {
    sign configurations.pom
}
// prepare publication tasks depends on existence of pom file
if (pomFile.exists()) {
    task preparePublication (dependsOn : [signArchives, signPom])
} else {
    task preparePublication (dependsOn : signArchives)
}
// getting pom signature
def getPomSignature = {
    return project.tasks.signPom.signatureFiles.collect{it}[0]
}
publishing {
    publications {
        // jar publication -- this will be executed last
        mainJar(MavenPublication) {
            from components.java
            [
                    [jarTask : sourceJar,  classifier : 'sources', extension : 'jar'],
                    [jarTask : javadocJar, classifier : 'javadoc', extension : 'jar']
            ].each {archive ->
                artifact (archive.jarTask) {
                    classifier = archive.classifier
                    extension  = archive.extension
                }
            }
            pom.withXml {
                Node node = asNode()
                node.children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    packaging 'jar'
                    licenses {
                        license {
                            name 'The Apache Software License, Version 2.0'
                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
                            distribution 'repo'
                        }
                    }
                }
                if (!project.ext.pomFile.exists()) {
                    writeTo(project.ext.pomFile)
                }
            }
        }
        // publication of jar signatures
        jarSignatures(MavenPublication) {
            getSignatureFiles().each {signature ->
                artifact (signature.archive) {
                    classifier = signature.classifier
                    extension  = signature.extension
                }
            }
        }
        // publication of pom signatures depends on existence of pom file.
        if (project.ext.pomFile.exists()) {
            'gpg-pom'(MavenPublication) {
                artifact (getPomSignature()) {
                    classifier = null
                    extension  = 'pom.asc'
                }
            }
        }
    }
    repositories {
        maven {
            // siwtch repository location by existence of pom file
            if (project.ext.pomFile.exists()) {
                // target repository
                url "file:/${project.projectDir}/artifacts"
            } else {
                // temporary repository
                url "file:/${project.projectDir}/tmp"
            }
        }
    }
}

そして、artifactを発行するために、

次のコマンドを二度発行します。

build.gradle
1
$ gradle clean pP publish

これで、maven repositoryへ署名ファイル付きのリリースができるようになります。

次回は、今回の記事の要約文を掲載する予定です。

Gradle 1.4以降から追加されているmaven-publishプラグインでは、まだmaven Central レポジトリーに上げられないようです。

こんにちわ、みけです。

あ、また、例によって記事が長いので、

結論だけ見たい人は前半部分だけを見て下さい。 – 大体5分以内。

で、gradleでのmaven centralへのライブラリー登録方法を知りたい方は中盤部分まで読んで下さい。 – トータル15分。

で、僕の強引なgradle遊びまで読みたい方は最後まで読むといいかもしれません。 – トータル30分。


前半部分

ここ数日gradle1.4以降についかされたmaven-publishプラグインを使って

maven centralへのライブラリー登録方法を調べていましたが、

maven-publishプラグインでのmaven central repoへの登録はまだサポートされてません

ようです。

元記事はこちらです。

以下、簡単な意訳+要約文

質問

maven-publishプラグインを使ってレポジトリー情報の設定と、それと別個に、singningプラグインを使ってartifactsへのサインを行うことができますが、それらを連携させることができません。

どのようにすればmaven-publishプラグインで、artifactsとサインファイル(.ascファイル)をアップロードするようにできますか?

回答

今のところmaven-publishプラグインを使ってartifactとsignatureをアップロードすることはサポートされていません。Gradleに「.asc」ファイル(サインしたファイル)が通常のartifactではなく、特別なartifactであることを伝える手段がないことが問題となっています。

こちらのロードマップを参照下さい。なお、この機能に関する優先順位は高くありません。

再質問

現在、古い方法でのアップロードはサポートされていますか?それとも手作業でやらないと駄目ですか?

回答

古い方法でのアップロードは利用できます。

提案

ありがとうございました。

ところで、ドキュメントの方で新しいプラグインではmaven centralにアップロード出来ないという記述がないので追加してもらえますか?

とのことで、maven-publishプラグインでのmaven central repoへのアップロードはまだ対応されていないようです。これはこれで従来のmavenプラグインよりも便利なので、maven centralへのアップロードも可能になって欲しいとろこです。


中盤部分

なお、日本語でも情報は入手出来ますが、念の為にこちらにも記述しておきます。

現状のgradleを用いたmaven centralへのアップロード方法

概要は、山本裕介氏のこちらの記事を参照して下さい。

また、gradleでの方法についてはこちらを参照して下さい。

両方の記事に共通していますが、Sonatypeからmaven centralにアップロードする場合は、

  • PGPによる署名の作成
  • 求められた規約の順守

が求められます。

なんでこんな面倒くさいのか?

maven centralにおいてあるライブラリーの品質や、pomなどの情報がバラバラで、一定の品質を保てなかったからのようです。

詳しくはこちらをお読み下さい(英語)。

central repositoryの品質向上のためにmaven central repoでは下記の規約を儲けています。

pom.xmlへの要求事項

  • <modelVersion>4.0.0を設定する
  • <groupId> – 申請するときにもgroupIdの申請が必要です。Sonatypeに申請するときはベースになるドメイン名で申請する必要があります。例えば、「org.mikeneck」は大丈夫ですが、「mikeneck」というgroupIdでは駄目です。
  • <artifactId> – ライブラリーの名前ですね。
  • <version> – バージョン番号です。なお、1.0.0-SNAPSHOTというようなSNAPSHOTというバージョンはSonatypeではmaven central repoには乗せてくれませんの、注意して下さい。
  • <packaging> – 基本的にはjarです。
  • <name> – プロジェクトの名前です。
  • <description> – ライブラリーに関する情報です。何をしてくれるライブラリーであるかを記述します。
  • <url> – プロジェクトのurlを設定します。
  • <licenses> – ライセンスに関する情報を設定します。中には下記の情報が一つ以上入っていることが求められます。
  • <license><name> – ライセンス名(ApacheライセンスとかLGPLとか)
  • <license><url> – ライセンス条項のurlを記述します
  • <license><distribution>repoを設定します。
  • <scm><url> – レポジトリーのurlを記述します。git-hubの場合はsshのアドレス設定します。bitbucketのMercurialの場合はレポジトリーをWebで見る場合のurlを記述します。
  • <scm><connection> – git-hubであればsshのところで得られるurlの先頭にscm:git:を加えます。bitbucketのMercurialの場合はWebで見る場合のアドレスの先頭にscm:hg:を付与します。
  • <developers> – 開発者情報を記入します。その中の構成は次のとおりです。
  • <developer><id> – 開発者のID。通名とかでも良いようです。僕の場合はmike_neckを記入します。
  • <developer><name> – 開発者の名前です。僕の場合はShinya Mochidaになります。
  • <developer><email> – 開発者のメールアドレスです。僕の場合はmike <at> mikeneck.orgとしています。

配布するファイルへの規約

  • <packaging>jarの場合にはjarファイルにはjavaクラスが入っていること
  • 名前がprojectname-version-javadoc.jarというjavadocのjarが入っていること
  • 名前がprojectname-version-sources.jarというソースのjarが入っていること
  • すべてのartifact(pom.xmlprojectname-version.jarprojectname-version-javadoc.jarprojectname-version-sources.jar)に対してPGPによる署名がなされていること
  • 公開鍵が公開鍵サーバーから取得可能になっていること

となっています。

また、何らかの理由でjavadocのjarやsourcesのjarが作られない場合でも、READMEというファイルを含んだjavadocのjarよsourcesのjarを作る必要があります。

gradleでmaven centralにアップロードするためのbuild.gradle

では、サンプルのbuild.gradleをここに上げておきます。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
['groovy', 'signing', 'maven', 'idea'].each {
    apply plugin : it
}
// project information
sourceCompatibility = jdkVersion
targetCompatibility = jdkVersion
group = 'org.jojo.sample'
version = '1.0'
// dependency management
repositories {
    mavenCentral ()
}
dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.1.3'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
}
// compile options
tasks.withType(Compile) {
    options.encoding = 'UTF-8'
}
// javadoc settings (making template locale en_US)
javadoc {
    options.locale = 'en_US'
}
// creating jars
task sourcesJar (type : Jar) {
    classifier = 'sources'
    from sourceSets.main.allSource
}
task javadocJar (type : Jar, dependsOn : javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}
// collect artifacts to be signed
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
}
// sign artifacts
signing {
    sign configurations.archives
}
// uploading artifacts
uploadArchives {
    repositories.mavenDeployer {
        beforeDeployment {MavenDeployment deployment ->
            signPom(deployment)
        }
        repository (url : sonatypeUrl) {
            authentication (
                    userName : sonatypeUsername,
                    password : sonatypePassword)
        }
        pom.project {
            name project.name
            packaging 'jar'
            description 'sample project'
            url projectUrl
            licenses {
                license {
                    name 'The Apache Software License, Version 2.0'
                    url 'http://www.apache.org/license/LICENSE-2.0.txt'
                    distribution 'repo'
                }
            }
            scm {
                url githubUrl
                connection "scm:git:${githubUrl}"
                developerConnection "scm:git:${githubUrl}"
            }
            developers {
                developer {
                    id 'jojo'
                    name 'Jonathan Joester'
                    email 'mike <at> mikeneck.org'
                }
            }
        }
    }
}

また、よく使いまわす変数についてはgradle.propertiesに書いておきます。

gradle.properties
1
2
3
jdkVersion=1.7
projectUrl=https://github.com/mike-neck/mike-neck.github.com
github=git@github.com:mike-neck/mike-neck.github.com.git

また、署名関連の変数などについては~/.gradle/gradle.propertiesに書いておきます。

gradle.properties
1
2
3
4
5
6
7
8
# siging information
signing.keyId=ABCD1234
signing.password=HOGEpassword00
signing.secretKeyRingFile=/Users/username/.gnupg/secring.gpg
# sonatype information
sonatypeUrl=https://oss.sonatype.org/service/local/staging/deploy/maven2/
sonatypeUsername=username
sonatypePassword=password

あとは、gradleコマンドでuploadArchivesを記述すれば、

Sonatypeの方にアップロードされます。

gradle.properties
1
$ gradle uploadArchives

なお、事前にSonatypeでのJIRAでissueを登録しておくことや、

Nexus UIで最終的なステージング操作をする必要があります。

参考までに、僕が以前作ったissueのリンクを張っておきます。

OSSRH-4119 request for creating repository for Graffiti-mike

中盤終わり


gradleであそぶコーナー

TBDと書きたいのですが…

上記の古いgradleでのアーカイブアップロードの方法は、

やや難点があります。

gradle.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
$ gradle tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.

Documentation tasks
-------------------
groovydoc - Generates Groovydoc API documentation for the main source code.
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
dependencies - Displays all dependencies declared in root project 'properties-builder'.
dependencyInsight - Displays the insight into a specific dependency in root project 'properties-builder'.
help - Displays a help message
projects - Displays the sub-projects of root project 'properties-builder'.
properties - Displays the properties of root project 'properties-builder'.
tasks - Displays the tasks runnable from root project 'properties-builder' (some of the displayed tasks may belong to subprojects).

IDE tasks
---------
cleanIdea - Cleans IDEA project files (IML, IPR)
idea - Generates IDEA project files (IML, IPR, IWS)

Upload tasks
------------
uploadArchives - Uploads all artifacts belonging to configuration ':archives'

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Other tasks
-----------
cleanIdeaWorkspace
install - Installs the 'archives' artifacts into the local Maven repository.
wrapper

Rules
-----
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.
Pattern: clean<TaskName>: Cleans the output files of a task.

To see all tasks and more detail, run with --all.

BUILD SUCCESSFUL

Total time: 10.478 secs

uploadArchivesのところを見ると、

archives configurationに登録されているすべてのartifactsをアップロードする

と書かれています。

ただ、これだと、ひとつのアップロードしか書いていくことができないので、

非常に面倒です。

たとえば、こういった局面があります。

  • in-houseリポジトリーにも登録する
  • maven centralにも登録する
  • 異なるartifactsをアップロードする
  • dependenciesにトリッキーなことをしているので、dependenciesの記述を書き換えたい

こういったケースでは、

一つ一つ別々のタスクとして記述をしていかないとできない場合があります。

gradleは柔軟性も求めるツールなので、

これらの要望も吸収して

簡単な記述でできるように常に進化を遂げています。

それを満たす機能が、今回のテーマのmaven-publishプラグインです。

maven-publishプラグインではarchives configuration以外の成果物も

柔軟に発行できますし、依存性を書き換えることもできます。

記述はこんな感じになります。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
['groovy', 'maven-publish'].each {
    apply plugin : it
}
// 中略
publishing {
    publications {
        ourMavenServer(MavenPublication) {
            from components.java
            artifact sourceJar
            pom.withXml {
                def node = asNode()
                node.removeNode(node.dependencies[0])
                asNode().children().last() + {
                    resolveStrategy = CLosure.DELEGATE_FIRST
                    // writing additional pom elements with builder style
                    name 'our-subproject'
                    description 'our-subproject description'
                    url 'https://www.google.com'
                }
                // overwrite dependencies
                asNode().dependencies[0].replaceNode {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    dependencies {
                        def dep = project.configurations.another.dependencies
                        dep.each {d ->
                            dependency {
                                groupId d.group
                                artifactId d.name
                                version d.version
                                scope 'compile'
                            }
                        }
                    }
                }
            }
        }
    }
    repositories {
        maven {
            name 'in-house'
            url 'https://repos.mycompany.com/nexus/service/local/staging/deploy/maven2/'
        }
    }
}

上記の例でやっていることは

  • ドキュメント読まないのでclassesとsourcesだけをアーカイブ化
  • ちろっとpom.xmlに情報を追加
  • dependenciesを書き換え

一般常識的に考えれば、dependenciesの書き換えはマズイと思われますが…

某有名なライブラリーのpom.xmlでありもしないartifactを参照しているライブラリーがあり、

compile configurationではプロジェクトのdependencyを指定するのではなく、

build.gradle
1
2
3
configurations {
    another
}

と異なるconfigurationを設定して、

プロジェクトのdependencyを設定する場合などがあります。

某有名ライブラリーとはorg.eclipse.jetty:jetty-serverというんですけどね…


さて、もう少し遊んでいるんですが、

それは別の記事にしますね。

Gradleのjavadocタスクでjavadocを英語で出力する方法

こんにちわ、みけです。

表題の件はこの記事の前半部分に書いています。

なので、必要な人は前半部分だけ読んで下さい。


大した話ではありませんが、

gradleのjavadocタスクで出力されるjavadocが日本語で出力される

のがちょっと残念な時があります。

javaプラグインを入れていれば、javadocタスクが自動で追加されます。

僕のような日本語環境でやっている人だと、

頑張ってjavadocを英語で書いても、

テンプレートが日本語で出力されてしまいます。

会社で日本語を使っていて、

javadocが日本語でないと困る場合は、

全然問題ないとおもいますが…

オープンソースなソフトウェアを開発している場合、

javadocが日本語だとなんか若干困ります。

(まあ、だいたいjavadocのテンプレートなんで、何が出力されているかなんてわかりますけどね…)

javadocタスクの設定でoptions.localeen_USを指定すればいいです

つまり、以下のとおりになります。

build.gradle
1
2
3
4
5
apply plugin : 'java'

javadoc {
    options.locale = 'en_US'
}

本題、終わり


ちなみに、僕の得意技はtypoなので、間違えてこんなビルドスクリプト書いてました。

(誤)

build.gradle
1
2
3
4
5
apply plugin : 'java'

javadoc {
    options.local = 'en_US'
}

これを実行したら、こんなエラーが出力されました。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ gradle javadoc
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8

FAILURE: Build failed with an exception.

* Where:
Script '/Users/mike/myprojects/sample/build.gradle' line: 4

* What went wrong:
A problem occurred evaluating script.
> No such property: local for class: org.gradle.external.javadoc.StandardJavadocDocletOptions
  Possible solutions: locale

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 8.941 secs

gradle曰く

localなんてオプションないよ、localeじゃない?

というわけで、

gradleさん正しい答えを教えてくれたりと、親切ですね。

本当に親切なツールです。

しかし、

本当に親切なのはgroovyの思想

です。

例えば次のようなgroovyのスクリプトがあるとします。

error.groovy
1
2
3
4
def range = 1..10
range.colect {
    println (it * it)
}

これを実行するとこういうエラーが出力されます。

error.groovy
1
2
3
4
5
6
$ groovy error.groovy
Caught: groovy.lang.MissingMethodException: No signature of method: groovy.lang.IntRange.colect() is applicable for argument types: (error$_run_closure1) values: [error$_run_closure1@639d564]
Possible solutions: collect(), collect(), collect(groovy.lang.Closure), collect(groovy.lang.Closure), collect(java.util.Collection, groovy.lang.Closure), collect(java.util.Collection, groovy.lang.Closure)
groovy.lang.MissingMethodException: No signature of method: groovy.lang.IntRange.colect() is applicable for argument types: (error$_run_closure1) values: [error$_run_closure1@639d564]
Possible solutions: collect(), collect(), collect(groovy.lang.Closure), collect(groovy.lang.Closure), collect(java.util.Collection, groovy.lang.Closure), collect(java.util.Collection, groovy.lang.Closure)
  at error.run(error.groovy:2)

存在しないメソッドを呼び出した時に、

Possible solutionsということで、

サジェストしてくれます。

また、有名な例ですが、Power Assertもあります。

error.groovy
1
2
def list = [1,1,2,3,4,5]
assert list - [1,3,6] == [1,2,4,5]

これを実行すると、このように表示されます。

error.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ groovy error.groovy
Caught: Assertion failed:

assert list - [1,3,6] == [1,2,4,5]
       |    |         |
       |    [2, 4, 5] false
       [1, 1, 2, 3, 4, 5]

Assertion failed:

assert list - [1,3,6] == [1,2,4,5]
       |    |         |
       |    [2, 4, 5] false
       [1, 1, 2, 3, 4, 5]

  at error.run(error.groovy:2)

ただ、求めている結果と実際の結果が違うという表示だけでなく、

実際の値を示してくれます。

つまり一言で言えば、

groovyの半分は優しさでできています

なお、この機能はもともとからgroovyにあったわけではなく、

Spockというテスティングフレームワークから採用された機能です。

groovyの思想では、こういった、(・∀・)イイネ!!な機能を

どんどん取り込んでいくというのがあると思っています。

まあ、元々、javaを良い感じで書きたいといった思想から生まれている言語ですし、

実際に、rubyなど他の言語のいいところを借りたりしているので、

後発の優位性を遺憾なく発揮しているわけですが…

Gradleプロジェクトは、gradleを使うよりも、gradlewを使うことがオススメです

みけです。

最近、Java開発者のgradleへの注目には眼を見張るものがあります。

gradle1.0-m2くらいから使っていた僕も、

参考になるような記事がたくさんあります。

みなさんありがとうございます。

gradleのすばらしさ

僕はgradleいいよという時に、いつも聞かれるのですが、

AntとMavenにくらべてgradleの何がいいの?

という質問があります。

DSLで書けるとか、

dependencyの指定が簡単とか、

まあ、各種ツールに精通すれば、

別にantでもmavenでも変わりません。

あえてそれでもgradleを選ぶ理由を上げるなら、

gradle wrapperの存在です。

チームで開発する場合、開発環境をチーム内で同じにしておくことが重要です。

メンバーの開発マシンの

antのバーション、mavenのバージョン、

これらが違ったということでビルドに失敗して、

その原因追求のために時間がかかるということも

よくある話です。

一方で、gradleでは

gradle wrapperを用いれば、

ビルドツールのバージョンに振り回されることはありません。

あまりピンとこないかもしれませんが、

実例をあげてみます。

gradle本体をIntelliJ IDEAに取り込む

gradle DSLを読んでいて、どうしてもわからないことがある場合、

gradle本体のソースを読むことがあります。

最新(2013/06/12時点)のソースを落としてきて、

gradle ideaタスクを実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ gradle --version

------------------------------------------------------------
Gradle 1.6
------------------------------------------------------------

Gradle build time: 2013年5月7日 9時12分14秒 UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.4 compiled on May 22 2012
Ivy: 2.2.0
JVM: 1.7.0_13 (Oracle Corporation 23.7-b01)
OS: Mac OS X 10.8.4 x86_64

$ gradle idea
:buildSrc:clean
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy
:buildSrc:processResources
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:checkstyleMain
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy
:buildSrc:processTestResources
:buildSrc:testClasses
:buildSrc:checkstyleTest UP-TO-DATE
:buildSrc:codenarcMain
:buildSrc:codenarcTest
:buildSrc:test
:buildSrc:check
:buildSrc:build

FAILURE: Build failed with an exception.

* Where:
Script '/Users/mike/IdeaProjects/gradle/gradle/gradle/integTest.gradle' line: 32

* What went wrong:
A problem occurred evaluating script.
> Failed to notify action.
   > Could not find property 'reports' on task ':announce:integTest'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 54.695 secs

最新のgradle(バージョン1.6)でエラーが発生します。

ビルドスクリプト:buildSrcプロジェクトのテスト中に落ちてしまいます。

一方、プロジェクトに付属しているwapperで実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$ ./gradlew --version

------------------------------------------------------------
Gradle 1.7-20130519231153+0000
------------------------------------------------------------

Build time:   2013-05-19 23:11:53 UTC
Build number: none
Revision:     9a7199efaf72c620b33f9767874f0ebced135d83

Groovy:       1.8.6
Ant:          Apache Ant(TM) version 1.8.4 compiled on May 22 2012
Ivy:          2.2.0
JVM:          1.7.0_13 (Oracle Corporation 23.7-b01)
OS:           Mac OS X 10.8.4 x86_64

$ ./gradlew idea
Deleting directory /Users/mike/.gradle/wrapper/dists/gradle-1.7-20130519231153+0000-bin/1cbtqldhq0muu2cto5pdcq66ee/gradle-1.7-20130519231153+0000
Unzipping /Users/mike/.gradle/wrapper/dists/gradle-1.7-20130519231153+0000-bin/1cbtqldhq0muu2cto5pdcq66ee/gradle-1.7-20130519231153+0000-bin.zip to /Users/mike/.gradle/wrapper/dists/gradle-1.7-20130519231153+0000-bin/1cbtqldhq0muu2cto5pdcq66ee
Set executable permissions for: /Users/mike/.gradle/wrapper/dists/gradle-1.7-20130519231153+0000-bin/1cbtqldhq0muu2cto5pdcq66ee/gradle-1.7-20130519231153+0000/bin/gradle
:buildSrc:clean
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy
:buildSrc:processResources
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:checkstyleMain
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy
:buildSrc:processTestResources
:buildSrc:testClasses
:buildSrc:checkstyleTest UP-TO-DATE
:buildSrc:codenarcMain
:buildSrc:codenarcTest
:buildSrc:test
:buildSrc:check
:buildSrc:build
:ideaModule

## 途中長いので略

:ui:ideaModule
:ui:idea
:wrapper:buildReceiptResource
:wrapper:ideaModule
:wrapper:idea

BUILD SUCCESSFUL

Total time: 2 mins 11.981 secs

という形で、

マシンにインストールされているgradleのバージョンの如何にかかわらず、

プロジェクトごとに最適化されたビルドを提供してくれます。

初めてRubyのモジュールを書いてみた

こんにちわ、みけです。

前回の記事『Rubyのmixinの話を読んでいたら、何故かjavaを書いていた』はあまりにも

rubyに対して失礼なので、

初心者らしく、ちゃんとmoduleを作ってみました。

ところで、僕の感心事は前回の記事でも書いたように、

rubyのmoduleはインスタンス変数にアクセスすることができるのか?

ということです。

で、結論から言えば、

rubyのmoduleはインスタンス変数にアクセスできました

となります。

moduleを作成

having_title.rb
1
2
3
4
5
6
7
8
9
10
# -*- codig: utf-8 -*-

module HavingTitle
  def title=(t)
    @title = t
  end
  def title
    @title
  end
end

moduleHavingTitleは単純にタイトルというものに

アクセスすることができるモジュールです。

先程も書きましたが、僕の疑問は@titleフィールドに、

インスタンス化することができないmoduleがアクセすることができるのか

ということです。

moduleincludeしたclassを作成

book.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding: utf-8 -*-

require './having_title'

class Book
  include HavingTitle

  attr_accessor :price

  def initialize(title, price)
    @price = price
    self.title = title
  end

  def print_info
    "Book[title : #{@title}, price : #{@price}]"
  end
end

タイトルというものを持っていそうなクラスということで

Bookというクラスをつくりました。

後はBookクラスをインスタンス化して、

フィールド@titleにアクセスできるかどうか確認出来れば

僕の疑問は解決出来ます。

irbで動作を確認

あ、まだrubyでのテストの書き方知らないので、

ここはirbで勘弁して下さい。

book.rb
1
2
3
4
5
6
irb(main):001:0> require './book'
=> true
irb(main):002:0> book = Book.new 'anti-oedipus', 4000
=> #<Book:0x007f90890b6e38 @price=4000, @title="anti-oedipus">
irb(main):003:0> book.print_info
=> "Book[title : anti-oedipus, price : 4000]"

というわけで、特に何のエラーも出ることなく、

フィールド@titleにアクセスできているようですね。

モジュールの機能の一つとしてのMix-in

まあ、『初めてのRuby』にはモジュールについて次のように書いてあります。

モジュールはクラスに似ています。モジュールは「インスタンス化できないクラス」のようなものです。 ClassクラスはModuleのサブクラスですから、「クラス = モジュール + インスタンス化能力」と言ってもよさそうです。 (Yugui著『初めてのRuby』(オライリー・ジャパン、2008年、p.159))

Javaしかわからない(Javaも詳しくはわからない)僕には、

abstract classと考えておけばよさそうです。

で、複数のモジュールを継承できるので、

なんか、アレですね…

JIRA6のチケットで他のチケットへのリンクを張る方法

こんにちわ、みけです。

微妙に表題の件に関して、30分くらいさまよったので、覚書き。

公式のドキュメントはこちらです。

JIRA User’s Guide > Working with an Issue > Linking Issues

JIRA6であるチケットから他のチケットへのリンクを張る方法

JIRAのissueの上にあるメニューからMore > Linkを選択します

当該チケットとの関係を選択して、関連するチケットを選択します

チケットが関連付けられているか確認します

えっ、Redmineならもっと簡単だって?

そうですね。

Rubyのmixinの話を読んでいたら、何故かjavaを書いていた

こんにちわ、みけです。

@suginoyさんに

『楽しい開発スタートアップRuby』を買っていただいたので、

今日はそれを某社オフィスで読んでいました。

Rubyのmix-inについての読んだ後の会話

  • 僕「mix-inよくわからん」
  • イケメン「多重継承できるやつでしょう」
  • 僕「mix-inってインスタンス変数とかにアクセスできるのかな?」
  • イケメン「できそうじゃね」
  • 僕「あ〜、Java8のdefault実装でもmix-inと同じ事できそうじゃね」
  • イケメン「いや、できないでしょう。default実装といっても、interfaceにフィールドを持つことができないわけだから」
  • 僕「え、できないのか、あー、そうかできないな。えっ、じゃあ、あれ、誰得なの?」

(多少脚色有り、あと会話内容忘れたので、結構適当)

で、

書いていたのがJava8のコードでした

Score.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.mikeneck.jdk8;

/**
 * @author mike
 */
public class Score {

    final private String title;

    final private int value;

    public Score(String title, int value) {
        this.title = title;
        this.value = value;
    }

    public String getTitle() {
        return title;
    }

    public int getValue() {
        return value;
    }
}
Grade.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.mikeneck.jdk8;

import java.util.List;

/**
 * @author mike
 */
public interface Grade {

    public List<Score> scores();

    default long getTotalScore () {
        return scores()
                .stream()
                .mapToLong(Score::getValue)
                .reduce(0l, Long::sum);
    }
}

ところで、このdefaultメソッド、

書いている途中はこんな感じでした。

IntelliJ IDEAではシンタックスをもっとよくできる場合は、

こういう形で通知してくれます。

紫の部分にカーソルをあてて、

Alt + Enter (mac の場合は option + enter)をすると、

操作の候補が表示されます。

ここではReplace lambda with method referenceを選択します。

すごいシンプルなコードになりました。

続いて、mapした結果をreduceしていきます。

初期値0lで、結果を合計したいので、次のような演算を書きます。

これはこれでまちがいでありません。

ところで、java.util.stream.Stream#mapToLongの戻り値である、

java.util.stream.LongStreamのjavadocを読むとこのように記述されています。

Api note : Sum, min, max, and average are all special cases of reduction. Summing a stream of numbers can be expressed as:

long sum = integers.reduce(0, (a, b) -> a+b);

or more compactly:

long sum = integers.reduce(0, Long::sum);

引用元 : javadoc (build-89)

つまり、java.util.function.LongBinaryOperatorの記述

1
(sum, item) -> sum + item

は、java.lang.Longstaticなメソッドsum(long, long)

置き換えることができるということです。

その結果、先のコードは次のようになりました(既出)。

さて、ここらへんのStream系の操作がエンタープライズな現場で使われるかどうかは

微妙ですが…(というのも、値の集計をするというのであれば、DBにさせたほうが早いので)

実際に現場で使うとなると、

高機能なIDE(Long::sumの部分はIntelliJでも簡略化できなかった)と

Stream系のApiの書き方を覚えておかないと、

相当効率悪くなるとおもいます。

というわけで、Java8の勉強をしたい方は是非

6月26日(水)の『Java8初心者勉強会』にご参加下さい。

Java8初心者勉強会 – http://atnd.org/event/java8beginner20130626tokyo

結論

あれ、rubyのmix-inについての話はどこ行った…

vimでrubyのコード書くの辛いです…

Groovyでpegdownをさわってみる

みけです。

前回に引き続き、Javaのmarkdownパーサーを試してみます。

で、今回は

pegdown

を試してみました。

サンプルコードは前回とほとんど変わりません。

pegdown-sample.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Grab('org.pegdown:pegdown:1.2.1')

def processor = new org.pegdown.PegDownProcessor()

def original = $/
This is Top Header
---

This is second Header
===

### This is topic

#### Lists Item

${(1..3).collect {"+ item${it}"}.join('\n')}

and

${(1..3).collect {"1. item${it}"}.join('\n')}

#### Links

+ [mike-neck's site](http://mike-neck.github.io/)
+ [mike-neck's dq site](http://mikeneckdq.blog.fc2.com/)
+ ![groovy image](http://groovy.codehaus.org/images/groovy-logo-medium.png)

#### Html Tags

<img src='//googledrive.com/host/0B4hhdHWLP7RRdHRGZ3ZrZU90Q00' style='width : 400px;'>

#### Codes

function `lists:reverse/1` returns a List.

tag `<em>` means emphasis

``groovy
def string = 'This is groovy code.'
``

#### emphasis

*em?*

**bold?**

#### Blockquotes

> This is a blockquotes
> from here.

/$

processor.markdownToHtml(original)

実行結果はこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<h2>This is Top Header</h2><h1>This is second Header</h1><h3>This is topic</h3><h4>Lists Item</h4>
<ul>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
</ul><p>and</p>
<ol>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
</ol><h4>Links</h4>
<ul>
  <li><a href="http://mike-neck.github.io/">mike-neck's site</a></li>
  <li><a href="http://mikeneckdq.blog.fc2.com/">mike-neck's dq site</a></li>
  <li><img src="http://groovy.codehaus.org/images/groovy-logo-medium.png"  alt="groovy image"/></li>
</ul><h4>Html Tags</h4><p><img src='//googledrive.com/host/0B4hhdHWLP7RRdHRGZ3ZrZU90Q00' style='width : 400px;'></p><h4>Codes</h4><p>function <code>lists:reverse/1</code> returns a List.</p><p>tag <code>&lt;em&gt;</code> means emphasis</p><p><code>groovy
def string = &#39;This is groovy code.&#39;
</code></p><h4>emphasis</h4><p><em>em?</em></p><p><strong>bold?</strong></p><h4>Blockquotes</h4>
<blockquote><p>This is a blockquotes from here.</p>
</blockquote>

markdownjとの比較

markdownjではこんな不具合が有りました。

  • ===がレンダーされない
  • 上の行にテキストがある状態で’—–‘がタグ<tr/>にレンダーされる
  • 当然ながら、GitHubっぽいコードスニペットはレンダーできない

一方、これらのうち、

  • ===がレンダーされない
  • 上の行にテキストがある状態で’—–‘がタグ<tr/>にレンダーされる

はgepdownでは解消されているようです。

残念ながら、

  • 当然ながら、GitHubっぽいコードスニペットはレンダーできない

というのはあります。

というわけで、pegdownの方が、良さげな感じがします。

Groovyでmarkdownjをさわってみる

こんにちわ、みけです。

このブログはOCTOPRESSで作っていますが、

どうしてもやりたい処理があったら、

rubyを勉強しないとアババな感じなので、

Javaでも同じようなものを作ってみたいと思っています。

OCTOPRESSっぽいツールに必要そうな機能

はこんな感じかなと思っています。

  • タスクを指定して実行する
  • git連携
  • previewできる
  • マークダウンをHTMLに変換する

これらのうち、最初のタスクを指定して実行するはGradleでやればよさそうです。

git連携はJGitを使えばできそうです。

previewはJettyだか、nettyだか、glassfishを使えばできそうです。

で、最後の

JavaでマークダウンをHTMLに変換する

はわからないので、

ちょっと試して見ることにしました。

まず、筆頭にくる markdownj を試してみることにしました

markdown-sample.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Grab('com.madgag:markdownj-core:0.4.1')

import com.petebevin.markdown.*

def proc = new MarkdownProcessor()

def original = $/
This is Top Header
---

This is second Header
===

### This is topic

#### Lists Item

${(1..3).collect {"+ item${it}"}.join('\n')}

and

${(1..3).collect {"1. item${it}"}.join('\n')}

#### Links

+ [mike-neck's site](http://mike-neck.github.io/)
+ [mike-neck's dq site](http://mikeneckdq.blog.fc2.com/)

#### Html Tags

<img src='//googledrive.com/host/0B4hhdHWLP7RRdHRGZ3ZrZU90Q00' style='width : 400px;'>

#### Codes

function `lists:reverse/1` returns a List.

tag `<em>` means emphasis

#### emphasis

*em?*

**bold?**

#### Blockquotes

> This is a blockquotes
> from here.

/$

proc.markdown(original)

これの結果は次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<p>This is Top Header
<hr /></p>

<p>This is second Header
===</p>

<h3>This is topic</h3>

<h4>Lists Item</h4>

<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>

<p>and</p>

<ol>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ol>

<h4>Links</h4>

<ul>
<li><a href="http://mike-neck.github.io/">mike-neck's site</a></li>
<li><a href="http://mikeneckdq.blog.fc2.com/">mike-neck's dq site</a></li>
</ul>

<h4>Html Tags</h4>

<p><img src='//googledrive.com/host/0B4hhdHWLP7RRdHRGZ3ZrZU90Q00' style='width : 400px;'></p>

<h4>Codes</h4>

<p>function <code>lists:reverse/1</code> returns a List.</p>

<p>tag <code>&lt;em&gt;</code> means emphasis</p>

<h4>emphasis</h4>

<p><em>em?</em></p>

<p><strong>bold?</strong></p>

<h4>Blockquotes</h4>

<blockquote>
  <p>This is a blockquotes
  from here.</p>
</blockquote>

今のところ、残念な点は

  • ===がレンダーされない
  • 上の行にテキストがある状態で’—–‘がタグ<tr/>にレンダーされる
  • 当然ながら、GitHubっぽいコードスニペットはレンダーできない

ちょっと残念なので、

他のマークダウンパーサーも試してみようと思います。

IntelliJ IDEAのJetGradleの使い方

Can’t live withoutSmart Java and Groovy IDE
with powerfull refactoring and code assistance.

こんにちわ、みけです。

いや、もう表題の件に関しては、

いろんなブログで既出なんですけど、 (と思ってググったら、あまりなかった(´・ω・`))

僕もよくわかってなかったので、

使ってみたときのメモを書いとくことにしました。

JetGradleってそもそも何よ?

IntelliJ IDEAに最初からバンドルされているgradleのサポートプラグインです。

では使い方を見て行きましょう。

(1)JetGradleタブを開きます。

画面右下の方にちょこんとある、JetGradleタブをクリックして開きます。

(2) Addアンカーをクリックして、build.gradleファイルを割り当てます。

ダイアログが出るので、今のプロジェクトのgradleファイルを割り当てます。

build.gradleファイルを選択するとこんなかんじになります。

(3) Taskタブをクリックします。

残念ながらこの段階ではタスク一覧が表示されません。

(4) 更新ボタンをクリックします。

先ほどのタブの左上の方にある更新ボタンをクリックします。

するとIntelliJがgradle┏( ^o^)┓ドコドコドコドコ┗( ^o^)┛ドコドコドコドコと聞いてきます。 (AA はテキトー)

右下の方にある、Gradle settingsアンカーをクリックします。

(5) GRADLE_HOMEを設定します。

こんなダイアログが表示されます。

僕はgradleのインストールをgvmでやっているので、

どこにGRADLE_HOMEがあるのかよくわからんので、 (覚えろよjk)

とりあえず、ターミナルで確認します。

GRADLE_HOMEの値をコピペします。

OKボタンをクリックすると、IntelliJがgradleファイルの読み込みを始めます。

読み込みが完了すると、タスク一覧が表示されます。

(6)JetGradleからタスクを実行してみる

とりあえず、テストを実行してみたいと思います。

タスク一覧から実行したいタスクを選択します。

ここではtestタスクを実行したいので、

testタスクを選択しています。

選択したタスクを長押し(Windowsの場合は右クリック)して、

実行する方法(デバッグとかrunとか)を選択します。

ここではRunを選択します。

テストが実行されました。

これで、一々IDEからターミナルに移動して

1
$ gradle test

とか打たなくてもいいですね。

(7)JetGradleの成果物はどこにあるの?

これ、疑問に思ったので、

あえてテストをこけさせてみました。 (こうすることでテストレポートの位置を示してくれる)

わざと落ちるテストを書いて、

Recent Taskからtestをじっこうします。

はい、落ちました。

で、テストレポートの場所を見ると、

ターミナルからテストを実行した時と変わらない場所に出力されていることがわかります。

以上、結論

JetGradle便利すなー