たくさんの自由帳

自作Androidライブラリを Maven Central へ公開する

投稿日 : | 0 日前

文字数(だいたい) : 11663

どうもこんばんわ。

保健室のセンセーとゴスロリの校医 攻略しました。前作はやってないです。
ロープライスだけどシナリオがちゃんとしていていいと思いました。おすすめ

Imgur

Imgur

みんなかわいい

Imgur

鈴ルートも近々発売みたいなので楽しみですね。くー

Imgur

本題

ちょっと前に書いたアプリのコア部分をライブラリに切り出した。

ライブラリを作ったのでせっかくなら公開しようというわけで、
Maven Centralへ公開し、他の人から使えるようにしてみます!

環境

用語集

なんだかよく分からん名前ばっか出るのでまとめます。
それぞれの立ち位置を理解するのでお腹いっぱいになりそう。

  • Sonatype Jira
    • ライブラリをアップロードするリポジトリを作成するため使う
      • 多分リポジトリ管理してる中の人とやり取りするのに使う
      • Jira チケットを切って担当者にお願いする
      • チケットを切るには Jira アカウントも作らないといけない
  • Sonatype OSSRH
    • Sonatype OpenSourceSoftware Repository Hosting の略らしい
    • まずはここにライブラリをアップロードする
  • MavenCentral
    • Sonatype OSSRH へアップロードしたライブラリが問題なければ MavenCentral へ公開する。
    • よく分からん

流れ

  • Sonatype (Atlassian) Jira のアカウントを作る
  • リポジトリの作成をお願いするJiraチケットを切る
    • グループID(ドメイン、GitHub Pages)のどれかを利用する
  • 署名に使うGPG鍵を作成する
  • Gradleを書く
  • リリースする
  • Sonatypeの管理画面からMavenCentralに公開する
  • おわり

なげえよ...

公式

https://central.sonatype.org/publish/publish-guide/

使うライブラリ

あり座椅子

https://github.com/gradle-nexus/publish-plugin

https://github.com/Kotlin/dokka

(Dokkaでjavadoc.jar 生成 : https://kotlinlang.org/docs/dokka-gradle.html#build-javadoc-jar)

参考にしました、ありがとうございます!

https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/

http://yuki312.blogspot.com/2021/08/maven-central-repository.html

Sonatype Atlassian Jira アカウントの作成

ここから作れます。
https://issues.sonatype.org/secure/Signup!default.jspa

Full name は適当でいいんじゃね、バカ正直にかく必要はなさそう雰囲気(知らんけどあとから変更できる)。
Usernameは変更できない上に管理画面へログインする際も使われるのでよく考えたほうが良さそう

https://central.sonatype.org/publish/manage-user/#change-my-full-name-e-mail-address-or-password

パスワードの条件難しくて草。最後の記号とかどうやって出すんだよ

You must specify a password that satisfies the password policy.
The password must contain at least 1 special character, such as &, %, ™, or É.

出来たらこうなる。作ったアカウントでログインして、適当に初期設定を進めます。

Imgur

Atlassian Jira 自体には日本語があります。なお日本語で対応してくれるとは言っていない。

Imgur

リポジトリを作ってもらうJiraチケットを切る

作成からチケットを切れます。

Imgur

プロジェクト、課題タイプ はそのままでおっけー

Imgur

そして以下の項目を埋めていきます。
Group Idってのはio.github.takusan23みたいなやつです。
自分で持ってるドメインとか使えるみたいですが、今回はGitHub Pagesのドメインを使います。
よくライブラリ導入時にドメインが逆になった文字列を入れると思うのですが、それです。

implementation("io.github.takusan23:conecocore:1.0.0") ← この io.github.takusan23がグループID

なまえ書くこと
要約リポジトリを作って欲しいよ~的なCreate repository for io.github.takusan23
説明ライブラリ作ったからMavenCentralで公開したいんだ!~的なI created an Android library that connects multiple videos. I would like to publish it using Maven Central.
Group IdグループID、GitHub Pagesのドメインとかio.github.takusan23
Project URLGitHubのリポジトリへのURLhttps://github.com/takusan23/Coneco
SCM urlProject URLで書いたURLに.gitをつければ良さそうhttps://github.com/takusan23/Coneco.git
Already Synced to Central初めてならNoで良いと思うNo

以下例です。

Imgur

作成を押すと、こんな風になるのでチケットの詳細ページへ飛びましょう。

Imgur

数十秒待っていると...

チケットへコメントが付きます。(Botなんですけどね初見さん)

Imgur

ここでグループIDの検証を行います、が特に難しいことをするわけでもなく、
自分のGitHubのリポジトリに作ったJiraチケットのチケット番号(OSSRH- から始まるやつ)の名前でリポジトリを作成しろって内容です。

というわけで作成して

Imgur

チケットのコメントに作ったことを知らせます。

Created repository for https://github.com/takusan23/OSSRH-79851

Imgur

チケットをOpenにしろって言われてるんだけど勝手にOpenになってた

Imgur

数分後に...

MavenCentralでio.github.takusan23が使えるようになったぞ!ってメールが来ました。歯磨き終わったら仕事終わってた。有能かよ

Imgur

これで、https://s01.oss.sonatype.org/ にログインできるようになります。
ログインはここです。
Username、パスワードはさっきのJiraアカウントと同じものを使えばOKです。

Imgur

Imgur

署名で使う鍵を作成する

公式:https://central.sonatype.org/publish/requirements/gpg/

私は Windowsユーザー なので Gpg4win を入れます。

https://gpg4win.org/index.html

Imgur

インストーラーの案内に従って入れましょう。

Imgur

インストールしたら開きます。GUIが付属してますので使っていきましょう。

Imgur

署名の中身

詳細設定は、詳細設定を押すと開きます。

なまえあたい
名前各自の名前
メールアドレス各自のメールアドレス
鍵の要素RSA +RSA (4096 ビット)
証明書の利用目的署名 (有効期限決めることも可能、今回はしない)
Protect the generated key with a passphraseチェックを入れる

Imgur

パスワードを入力します。

Imgur

できました。

Imgur

バックアップと失効証明書の作成

一応作成します。

バックアップはキーを右クリックしてコンテキストメニューを出し、Backup Secret Keys...で出来ます。

Imgur

失効証明書を作っておくと、万が一秘密鍵が漏れた場合に無効にできます。
コンテキストメニューを開き、詳細を押します。

Imgur

そしたらGenerate revocation certificateを押すことで発行できます。

Imgur

公開鍵を鍵サーバーへアップロードする

GUIでやる方法がわからんかったので、コマンドプロンプトでやります。
コマンドプロンプト、PowerShell等を起動して、まず鍵一覧を出します

gpg --list-keys

そしたら長い16進数があると思うので、この16進数の最後から8文字分をコピーしておきます。

次に、以下のコマンドを叩いてアップロードします。

gpg --keyserver keyserver.ubuntu.com --send-keys <コピーした16進数>

Imgur

署名鍵をBase64にする

最後にライブラリの署名で使うので、秘密鍵をBase64で書き出しておきます。
秘密鍵なので扱いには十分気をつけてね!!!!

macOSとかだとワンライナーみたいなんですが、Windowsの場合はちょっとめんどいですね...
適当なフォルダを作りその中で、PowerShellなどを起動して...

gpg --export-secret-keys コピーした16進数 > export_secret
certutil -f -encode export_secret export_secret_base64

export_secret_base64ファイルができていれば成功です。Base64なファイルは文字なのでテキストエディタで開けます。
最初の-----BEGIN CERTIFICATE-----と最後の-----END CERTIFICATE-----を消して、改行も消します

いよいよライブラリを公開するためのGradleタスクを書いていく...

2023/02/19 めっちゃ間違えてました。すいませn。ソースとjavadocが入るように修正しました。

2024/03/29 Android Gradle Plugin の更新でまた動かなくなってました。不要な箇所があるので追記読んでください。

ルート (.idea がある場所) の build.gradle.kts

まずルートにある build.gradle.kts へ書き足します。
ktsで書いた場合、別ファイルにスクリプトを書いてapply fromする方法が使えないみたいなので、直接書くしかないです。
(参考にした記事では別スクリプトに分けてた、あれ?ktsに移行するメリットマジでなくない...)

https://github.com/gradle/kotlin-dsl-samples/issues/1287

buildscript {
    val kotlinVersion: String by extra("1.7.10")
    val composeVersion: String by extra("1.3.1")
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id("com.android.application").version("7.4.0").apply(false)
    id("com.android.library").version("7.4.0").apply(false)
    id("org.jetbrains.kotlin.android").version("1.7.10").apply(false)
    // ドキュメント生成と Maven Central へ公開をしてくれるやつ
    id("org.jetbrains.dokka").version("1.7.10")
    id("io.github.gradle-nexus.publish-plugin").version("1.1.0")
}
 
tasks.register("clean") {
    doFirst {
        delete(rootProject.buildDir)
    }
}
 
// ライブラリ署名情報がなくてもビルドできるようにする
extra["signing.keyId"] = ""
extra["signing.password"] = ""
extra["signing.key"] = ""
extra["ossrhUsername"] = ""
extra["ossrhPassword"] = ""
extra["sonatypeStagingProfileId"] = ""
 
// 署名情報を読み出す。開発環境では local.properties に署名情報を置いている。
val secretPropsFile = project.rootProject.file("local.properties")
if (secretPropsFile.exists()) {
    // 読み出して、extra へ格納する
    val properties = java.util.Properties().apply {
        load(secretPropsFile.inputStream())
    }
    properties.forEach { name, value -> extra[name as String] = value }
} else {
    // システム環境変数から読み出す。CI/CD 用
    extra["ossrhUsername"] = System.getenv("OSSRH_USERNAME")
    extra["ossrhPassword"] = System.getenv("OSSRH_PASSWORD")
    extra["sonatypeStagingProfileId"] = System.getenv("SONATYPE_STAGING_PROFILE_ID")
    extra["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
    extra["signing.password"] = System.getenv("SIGNING_PASSWORD")
    extra["signing.key"] = System.getenv("SIGNING_KEY")
}
 
// Sonatype OSSRH リポジトリ情報
nexusPublishing.repositories.sonatype {
    stagingProfileId.set(extra["sonatypeStagingProfileId"] as String)
    username.set(extra["ossrhUsername"] as String)
    password.set(extra["ossrhPassword"] as String)
    nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
    snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
}

ライブラリの方の build.gradle.kts

こっちも同様直接書くしかないです。
ライブラリ名ライブラリの説明の部分や、
作者とかライセンスとかが書いてある下の部分は各自書き換えてください。

なまえせつめい
groupIdSonatype OSSRH のJiraチケットで決めたのと同じグループID
artifactIdライブラリ名とも言う
versionバージョン
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    // ライブラリに同梱するドキュメント生成器
    id("org.jetbrains.dokka")
    // ライブラリ作成に必要
    `maven-publish`
    signing
}
 
// ライブラリバージョン
val libraryVersion = "1.0.2"
// ライブラリ名
val libraryName = "conecocore"
// ライブラリの説明
val libraryDescription = "It is a library that connects multiple videos into one."
 
android {
    compileSdk = 33
    namespace = "io.github.takusan23.conecocore"
 
    defaultConfig {
        minSdk = 21
        targetSdk = 33
 
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFile("consumer-rules.pro")
    }
 
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
 
dependencies {
 
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
 
    implementation("androidx.core:core-ktx:1.7.0")
    implementation("androidx.appcompat:appcompat:1.4.1")
 
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
 
val androidSourcesJar = tasks.register<Jar>("androidSourcesJar") {
    archiveClassifier.set("sources")
    from(android.sourceSets["main"].java.srcDirs)
}
 
tasks.dokkaJavadoc {
    outputDirectory.set(File(buildDir, "dokkaJavadoc"))
}
 
val javadocJar = tasks.register<Jar>("dokkaJavadocJar") {
    dependsOn(tasks.dokkaJavadoc)
    from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
    archiveClassifier.set("javadoc")
}
 
artifacts {
    archives(androidSourcesJar)
    archives(javadocJar)
}
 
signing {
    // ルート build.gradle.kts の extra を見に行く
    useInMemoryPgpKeys(
        rootProject.extra["signing.keyId"] as String,
        rootProject.extra["signing.key"] as String,
        rootProject.extra["signing.password"] as String,
    )
    sign(publishing.publications)
}
 
afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("maven") {
                groupId = "io.github.takusan23"
                artifactId = libraryName
                version = libraryVersion
                if (project.plugins.hasPlugin("com.android.library")) {
                    from(components["release"])
                } else {
                    from(components["java"])
                }
                artifact(androidSourcesJar)
                artifact(javadocJar)
                pom {
                    // ライブラリ情報
                    name.set(artifactId)
                    description.set(libraryDescription)
                    url.set("https://github.com/takusan23/Coneco/")
                    // ライセンス
                    licenses {
                        license {
                            name.set("Apache License 2.0")
                            url.set("https://github.com/takusan23/Coneco/blob/master/LICENSE")
                        }
                    }
                    // 開発者
                    developers {
                        developer {
                            id.set("takusan_23")
                            name.set("takusan_23")
                            url.set("https://takusan.negitoro.dev/")
                        }
                    }
                    // git
                    scm {
                        connection.set("scm:git:github.com/takusan23/Coneco")
                        developerConnection.set("scm:git:ssh://github.com/takusan23/Coneco")
                        url.set("https://github.com/takusan23/Coneco")
                    }
                }
            }
        }
    }
}

追記 Android Gradle Plugin 最新バージョン追従

なんか見ないうちに、公式ドキュメントに、ライブラリの作り方が言及されるようになってる。
変更点ですが、

  • Android Gradle Pluginが元のソースコードを同梱するようになったので、自分でtaskを書かなくて良い
    • withSourcesJar()withJavadocJar()を使えばいい(いつから追加されたのこれ???)
  • 同様に、JavadocAGPが勝手に生成して同梱するようになったので、自分でdokkaをセットアップする必要はなくなった
  • それに伴い、build.gradleに書いている記述も修正が必要

まずは、ルートのbuild.gradle.ktsからdokkaは不要なので消しちゃって

plugins {
     alias(libs.plugins.kotlin.android).apply(false)
     // akaricore ライブラリ公開で使う
     alias(libs.plugins.gradle.nexus.publish.plugin)
-    alias(libs.plugins.jetpbrains.dokka)
-}
-
-subprojects {
-    apply(plugin = "org.jetbrains.dokka")
 }

そして、ライブラリの方のbuild.gradle.ktsを以下のように書き直します。
namespaceとかは各自違うと思う。コメントは不要なので消していいよ

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    // Maven Central に公開する際に利用
    `maven-publish`
    signing
}
 
// ライブラリ公開は Android でも言及するようになったので目を通すといいかも
// https://developer.android.com/build/publish-library/upload-library
// そのほか役に立ちそうなドキュメント
// https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html
// https://github.com/gradle-nexus/publish-plugin
 
// OSSRH にアップロードせずに成果物を確認する方法があります。ローカルに吐き出せばいい
// gradle :akari-core:publishToMavenLocal
 
android {
    namespace = "io.github.takusan23.akaricore"
    compileSdk = 34
 
    defaultConfig {
        minSdk = 21
        targetSdk = 34
 
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
    }
 
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
 
    // どうやら Android Gradle Plugin 側で sources.jar と javadoc.jar を作る機能が実装されたそう
    publishing {
        singleVariant("release") {
            withSourcesJar()
            withJavadocJar()
        }
    }
}
 
// ライブラリ
dependencies {
 
    implementation(libs.androidx.core)
    implementation(libs.androidx.appcompat)
    implementation(libs.kotlinx.coroutine)
    testImplementation(libs.junit)
    androidTestImplementation(libs.kotlinx.coroutine.test)
    androidTestImplementation(libs.androidx.test.junit)
    androidTestImplementation(libs.androidx.test.espresso.core)
}
 
// ライブラリのメタデータ
publishing {
    publications {
        create<MavenPublication>("release") {
            groupId = "io.github.takusan23"
            artifactId = "akaricore"
            version = "2.0.0-alpha01"
 
            // afterEvaluate しないとエラーなる
            afterEvaluate {
                from(components["release"])
            }
 
            pom {
                // ライブラリ情報
                name.set("akaricore")
                description.set("AkariDroid is Video editor app in Android. AkariDroid core library")
                url.set("https://github.com/takusan23/AkariDroid/")
                // ライセンス
                licenses {
                    license {
                        name.set("Apache License 2.0")
                        url.set("https://github.com/takusan23/AkariDroid/blob/master/LICENSE")
                    }
                }
                // 開発者
                developers {
                    developer {
                        id.set("takusan_23")
                        name.set("takusan_23")
                        url.set("https://takusan.negitoro.dev/")
                    }
                }
                // git
                scm {
                    connection.set("scm:git:github.com/takusan23/AkariDroid")
                    developerConnection.set("scm:git:ssh://github.com/takusan23/AkariDroid")
                    url.set("https://github.com/takusan23/AkariDroid")
                }
            }
        }
    }
}
 
// 署名
signing {
    // ルート build.gradle.kts の extra を見に行く
    useInMemoryPgpKeys(
        rootProject.extra["signing.keyId"] as String,
        rootProject.extra["signing.key"] as String,
        rootProject.extra["signing.password"] as String,
    )
    sign(publishing.publications["release"])
}

変更点ですが、

  • android { }の中にpublishing { }を追加した
  • ソースコードとドキュメント生成のtaskを消した、artifacts { }も消した、
  • afterEvaluate { }fromだけ移動。
  • 多分fromでソースとjavadocが入るので、artifact()は書かなくていい
  • signing { }を一番最後に

全部の差分はこちらから
https://github.com/takusan23/AkariDroid/commit/54e1dd4329569bda09bacdd090b34e78a65ab892

local.properties に認証情報を書き込む

このファイルはgitの管理下にしてはいけません。絶対人には見せちゃだめですよ。
なんか書いてあるかもだけどその下にかけばいいです。

# Git管理下に入れてはぜっっったいダメです
# 絶対秘密です
# 鍵IDの最後8桁
signing.keyId=xxxxxx
# パスワード
signing.password=password
# 秘密鍵のBase64
signing.key=xxxxxxxxx
# Sonatype OSSRH のユーザー名
ossrhUsername=takusan_23
# Sonatype OSSRH のパスワード
ossrhPassword=password
# Sonatype ステージングプロファイルId
sonatypeStagingProfileId=8320....

追記 ユーザーネーム、パスワードでログインできなくなった

こちらを参考に、ユーザートークンなるもの作ってください。

https://takusan.negitoro.dev/posts/maven_central_ossrh_upload_401_error/

ステージングプロファイルId どこやねん

Sonatype OSSRH の nexus repository manager へログインします。
https://s01.oss.sonatype.org

そしたら、左側にある中から、Staging Profilesを押して、自分の名前の部分を押します。
押したら、ブラウザのアドレス欄を見ます。stagingProfiles;のあとの16進数がステージングプロファイルIdになります。

Imgur

公開するぞ!!!!

といっても直接MavenCentralに公開されるわけではなく、一旦Sonatype OSSRHnexus repository managerへアップロードされます。
その後問題がなければ公開という流れになります。

Android Studioの右側にあるGradleを押してぞうさんのマーク🐘を押して、以下のコマンドを叩きます。
参考にした記事とコマンドが変わってますね。 https://github.com/gradle-nexus/publish-plugin

gradle :ライブラリ名(モジュール名):publishToSonatype

Imgur

数十秒後に終了しました。
プラグイン作ったやつすげえ!

Imgur

Sonatype OSSRH nexus repository manager の ステージングリポジトリを見に行く

またさっきのリポジトリのURL https://s01.oss.sonatype.org/ を開いて、Staging Repositoriesを押します。 押したら、Refreshを押して最新の状態にしましょう。

どうですか?ありましたか!?

Imgur

問題がなければCloseを、やり直したい場合はDropで消せます。

Close を押す

Closeを押すと、MavenCentralへ公開する?準備が始まります。
自動で公開されるわけではないので、まだ安心できますね。

Imgur

しばらく待っているとReleaseが押せるようになっていました。
いくぞおおおおお、新曲!

Imgur

Confirmを押してリリースです!

Imgur

数時間後に https://repo1.maven.org/maven2/ から見れるようになるみたいなので楽しみですね。

公開されたか見る

https://repo1.maven.org/maven2/ を開いて辿っていけばいいです。 あった!!!

Imgur

search.maven.org でも確認する

さらに待ってると、 https://search.maven.org で検索出来るようになってます。

Imgur

うおおおお、なんかすごい

Imgur

ライブラリの詳細画面はこんな感じ、導入方法とか書いてある。

Imgur

使う側になる

プロジェクトレベル(appフォルダ内)のbuild.gradleに書き足すと追加できます。やったぜ

implementation '<グループId>:<ライブラリ名(アーティファクトId)>:<バージョン>'

今回作ったライブラリだとこうですね。

// build.gradle
implementation 'io.github.takusan23:conecocore:1.0.0'
 
// build.gradle.kts の場合
implementation("io.github.takusan23:conecocore:1.0.0")

これで他の大手ライブラリのように導入できます。くっそ大変だなこれ...

Imgur

(なんか別にドキュメントを生成しないとダメみたいですね...)

ライブラリを入れた際に一緒にXXX-sources.jarがダウンロードされるので、IDE上でもそのまま表示されるようになります。

Imgur

Imgur

Imgur

他にライブラリを公開したい場合

すでにリポジトリがある(Sonatype OSSRH nexus repository manager が使える)ので、build.gradle.ktsのところからやればいいと思います。

ソースコード

どうぞ

https://github.com/takusan23/Coneco

https://github.com/takusan23/AkariDroid

おわりに

4月も終わりますね...