mike、mikeなるままに…

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

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というんですけどね…


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

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