📖

Androidライブラリを2024/2/1以降にMavenCentralに新しくpublishしたい

2024/03/03に公開

背景

久々にMavenCentralに新規ライブラリを新規namespaceにアップロードしようとしたら詰まったのでメモです。
(スクショ撮ったり丁寧に解説してなくてすみません)

公式サイトに一応全て書いてあるが、今までEarly Accessだった自動化されたpublish方式が2024/2/1以降に一般公開されたらしい。
https://central.sonatype.org/register/central-portal/

また、今までの方法も段階的に移行予定らしい。
とりあえず、3/12にはjiraでのnamespace承認プロセスはクローズされる予定らしい。
https://central.sonatype.org/news/20240301_changes_to_account_management/

とりあえず新規でnamespaceをとる人以外はしばらくは今まで通りの方法が使えます。

GPGでの署名関連は特に言及しないですが、必要なファイルや環境変数は設定しておいてください。

今までの方法

見たい方用

流れ

  1. https://issue.sonatype.org (jira)にissueを立てて人と交渉して、namespaceを取得
  2. https://oss.sonatype.org/ (Nexus repository manger) でバイナリをアップロードしたり、maven形式でのupload(mavenでいうpublish)を行うことで公開準備(staging)ができる。
  3. oss.sonatype.orgかAPI経由でStagingをclose and releaseすることで maven central repositoryに公開できた

まだドキュメントはあります。

gradleでフロー化

Androidはgradleを使っているので、archiveやuploadはgradle経由でやりたい。
だいたい以下のようにやれば良い。
https://medium.com/@vivekvashistha/how-to-publish-android-libraries-on-maven-central-in-2023-a96e3c327008

gradle公式のmaven-publishプラグインとsigningプラグインを使用するのが好き。
あと、Nexus repository managerはuploadごに自動publishできないので、publishするためにAPIを叩くところも自動化できると良さそう。
公式には特にpluginは出てませんでした。

今でも動いてるリリースフローのファイルイメージは下記。
(簡略化してたり、groovy製のをktsに直したりしたので動かなかったら直してください)

build.gradle.kts
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")

    id("maven-publish")
    id("signing")
    id("io.codearte.nexus-staging")
}

group = "com.example"
version = "0.0.1"

android {
    namespace = "com.example.library"
    compileSdk = 34

    defaultConfig {
        minSdk = 26
    }
    // ...
    publishing {
        // AGP 7.1からは下記が使えて便利だけどうまく動かないこともある
        singleVariant("release") {
            withJavadocJar()
            withSourcesJar()
        }
    }
}

dependencies {
     // ...
}

// "release" variantが取得できないとconfigできないから。
afterEvaluate {

    publishing {
        publications {
            register<MavenPublication>("maven") {
                groupId = project.group as String
                artifactId = "library"
                version = project.version as String
                from(components["release"])

                pom {
                    name = "xxx"
                    description = "xxx"
                    url = "https://github.com/xxx/xxx"
                    licenses {
                        license {
                            name = "The Apache License, Version 2.0"
                            url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
                        }
                    }
                    developers {
                        developer {
                            id = "xxx"
                            name = "xxx"
                            email = "xxx@xxx"
                        }
                    }
                    scm {
                        connection = "scm:git:https://github.com/xxx/xxx.git\""
                        developerConnection = "scm:git:ssh://github.com/xxx/xxx.git"
                        url = "https://github.com/xxx/xxx"
                    }
                }
            }
        }
        repositories {
            maven {
                url = URI("https://oss.sonatype.org/service/local/staging/deploy/maven2")
                credentials {
                    username = "xxx"
                    password = "xxx"
                }
            }
        }
    }
    signing {
        // 必要に応じてkeyidやpassphraseはpropertiesやenv等で設定
        sign(publishing.publications["maven"])
    }
}
nexusStaging {
    packageGroup = project.group
    stagingProfileId = "xxx"
    username = "ossrhUsername"
    password = "ossrhPassword"
}

実行イメージ

# maven-publishを行う。
$ ./gradle publish

# Nexus repository managerでstagingされたものをreleaseする
$ ./gradlew closeAndReleaseRepository

でもこれはそのうち廃止されるかもしれない。

2024/2/1以降の方法

流れ

  1. https://central.sonatype.com/ (昔のbintray的なツール)でアカウント作成してnamespace取得まで自動でできる
  2. https://central.sonatype.com/ でpublishを作成してバイナリをアップロードしたり、maven形式のファイルをAPI経由でupload
  3. uiからやった場合や自動publishしなかった場合はUIかAPI経由でpublishリクエスト

ドキュメント

gradleでフロー化

すでに書かれていますが、Android版じゃないので補足的に書きます。
https://zenn.dev/orangain/articles/publish-to-maven-central-using-gradle#fn-c896-2
また、公式に言及されてる野良プラグインを使ってますが、うまく動かなかったので、一旦curlで代用してます。
今度は公式のgradle plugin作ってくれるぽいこと書いてるのでひっそりと期待します。

やっぱり、gradle公式のmaven-publishプラグインとsigningプラグインを使用するのが好きなのでそうしてます。

build.gradle.kts
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")

    id("maven-publish")
    id("signing")
}

group = "com.example"
version = "0.0.1"

android {
    // ...
    publishing {
        singleVariant("release") {
//            withJavadocJar() // こっちだとsigningに間に合わないので諦めて空にする
            withSourcesJar()
        }
    }
}

dependencies {
    // ...
}

// 空のjavadocjarを生成するタスク
tasks.register<Jar>("javadocEmptyJar") {
    archiveClassifier = "javadoc"
}
// uploadするためのzipを生成するタスク
tasks.register<Zip>("makeArchive") {
    dependsOn("publishMavenPublicationToMavenRepository")
    from(layout.buildDirectory.dir("repos/com/example/library/$version"))
    into("com/example/library/$version")
}

afterEvaluate {
    publishing {
        publications {
            register<MavenPublication>("maven") {
                groupId = project.group as String
                artifactId = "library"
                version = project.version as String
                from(components["release"])
                artifact(tasks["javadocEmptyJar"])

                pom {
                    name = "xxx"
                    description = "xxx"
                    url = "https://github.com/xxx/xxx"
                    licenses {
                        license {
                            name = "The Apache License, Version 2.0"
                            url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
                        }
                    }
                    developers {
                        developer {
                            id = "xxx"
                            name = "xxx"
                            email = "xxx@xxx"
                        }
                    }
                    scm {
                        connection = "scm:git:https://github.com/xxx/xxx.git\""
                        developerConnection = "scm:git:ssh://github.com/xxx/xxx.git"
                        url = "https://github.com/xxx/xxx"
                    }
                }
            }
        }
        repositories {
            maven {
                // 本当はmavenLocalで良いはずだが、checksumファイルが生成されず怒られるので別ディレクトリにpublish
                url = uri(layout.buildDirectory.dir("repos"))
            }
        }
    }
    signing {
        // 必要に応じてkeyidやpassphraseはpropertiesやenv等で設定
        sign(publishing.publications["maven"])
    }
}

雑な解説

javadocについて

AGP 7.1以降に追加された withJavadocJar はartifactsにjavadocJarを追加してくれるので便利だが、内部でdokkaを使用してるぽく、非同期になるからかsigningされなかった。
そのため、普通にdokka等で生成するか空のjarを含めれば良い。
このサンプルは空のjarにしている。(未公開API等で空にするのは許されているが、別に推奨しないです)

upload用のzip作成

maven-publishプラグインでもuploadできるかもしれないが、試してないです。
公式には別エンドポイントとなっているぽいので、一度zip化するタスクを用意しています。

最初はmavenLocalへpublishしてからzipしてましたが、mavenLocalだとchecksumファイルが生成されなくて怒られるので、一度build/配下にpublishしてzipにしています。

実行イメージ

# localにmaven-publishしてupload用のzipを作る
$ ./gradlew makeArchive

# Central Portalにuploadしてpublishする
# 認証用 keyの生成
$ echo "username:password" | base64
xxx
$ curl --request POST \
  --verbose \
  --header 'Authorization: Bearer xxx' \
  --form bundle=@library/build/distributions/library-0.0.1.zip  \
  'https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC'

curlやbase64部分はgradle taskとしても良さそうです。
野良プラグインでも良いと思います。

最後のupload時にpublishingTypeは無指定時はautoになる、とドキュメントに書かれていましたが、自動でpublishされなかったため、明示したほうが良さそうです。

感想

新しいcentral portalは今まで下記の点で改善してそうと思いました。

  • jiraやnexusでツールが分割されてない
  • namespace認証が自動化されている
  • upload時に自動publishが選べる
  • 結果を取得できるwebhook機能がある?

たくさん進歩があっていいですね!(でも廃止されたjcenter, bintrayに近くなったなという印象ですw やっと追いついた?)

でも、gradleユーザ的にはpluginがなく情報がイマイチまとまっていないので、改善するといいなと思いました。
あとは既存のツールを利用したユーザも移行されていくらしいので、そこはスムーズになるといいと願っています。

Discussion