Недавно у меня появилось желание попробовать создать свою собственную Android библиотеку и выложить ее на Maven Central репозиторий.

Это оказалось не так просто, как я думал. В интернете полно непонятных статей на эту тему, в которых легко запутаться.

Я решил поделиться процессом публикации моей библиотеки Awesome-Buttons.

Ну что ж, приступим.

Регистрация на Sonatype JIRA и создание Issue

Перейдите на сайт Sonatype JIRA и создайте себе аккаунт.

Теперь войдите в аккаунт и создайте новый Issue:

Заполните всю необходимую информацию.

Обратите внимание на Group Id .Обычно в качестве его указывается доменное имя в обратном порядке.

Если у вас есть свой сайт или сайт библиотеки, то вы можете использовать его доменное имя (например: ru.freeit256), но для этого нужно будет дополнительно прописать запись в вашем регистраторе домена (после создания Issue вы увидите информацию как это сделать).

Я использовал другой подход: указал свой Github в качестве Group Id

Также для того, чтобы использовать Github в качестве Group Id вам нужно создать пустой репозиторий с именем OSSRH-74088

Не забудьте отправить ваше Issue на проверку и дождаться статуса CLOSED

Создаем Android библиотеку в Android Studio

Сначала создайте любое пустое приложение с app модулем.

Затем вы можете либо добавить модуль Android библиотеки, либо изменить текущий модуль на библиотечный. Читайте об этом на официальном сайте

Генерации GPG ключа

Перед публикацией необходимо сгенерировать пару GPG ключей.

Для генерации ключей выполните следующую команду:

 gpg --full-gen-key

Основные параметры при создании ключей:

  • формат шифрования: RSA and RSA

  • размер: 4096 бит

  • срок годности ключа можно указать 0 (никогда не истечет)

  • имя, email и комментарий укажите свои

Чтобы посмотреть все сгенерированные ключи выполните команду:

gpg --list-keys

Обратите внимание, последние 8 символов это ID ключа:

Для подписи библиотеки мы будем использовать приватный ключ, а чтобы пользователи смогли удостовериться, что библиотека принадлежит нам, мы должны выложить публичный ключ на общедоступный сервер, например keyserver.ubuntu.com:

// укажите свой ID ключа
gpg --keyserver keyserver.ubuntu.com --send-keys 0F11924A

Чтобы получить приватный ключ для подписи нужно выполнить:

// вы указываете свой email, который юзали, когда создавали ключ
gpg --armor --export-secret-keys dmitry.tehnicov@gmail.com | awk 'NR == 1 { print "GPG_SIGNING_KEY=" } 1' ORS='\\n'

Вуаля! Позже он нам понадобится.

Настройка библиотеки для публикации:

Добавляем плагин io.github.gradle-nexus в корневой файл build.gradle:

buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.0"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
    }
}

plugins {
  	// плагин для автоматизации публикации нашей либы
    id "io.github.gradle-nexus.publish-plugin" version "1.1.0"
}

// позже мы добавим скрипты для публикации
apply from: "${rootDir}/scripts/publish-root.gradle"

task clean(type: Delete) {
    delete rootProject.buildDir
}

Далее нам нужно создать два скрипта для модуля и корня нашей библиотеки.

Создадим в корне проекта папку scripts, и добавим новый скрипт publish-module.gradle:

apply plugin: 'maven-publish'
apply plugin: 'signing'

task androidSourcesJar(type: Jar) {
    archiveClassifier.set('sources')
    if (project.plugins.findPlugin("com.android.library")) {
        // For Android libraries
        from android.sourceSets.main.java.srcDirs
        from android.sourceSets.main.kotlin.srcDirs
    } else {
        // For pure Kotlin libraries, in case you have them
        from sourceSets.main.java.srcDirs
        from sourceSets.main.kotlin.srcDirs
    }
}

artifacts {
    archives androidSourcesJar
}

afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                // мы позже добавимм эти константы в другом файле
                groupId PUBLISH_GROUP_ID
                artifactId PUBLISH_ARTIFACT_ID
                version PUBLISH_VERSION

                // Two artifacts, the `aar` (or `jar`) and the sources
                if (project.plugins.findPlugin("com.android.library")) {
                    from components.release
                } else {
                    from components.java
                }

                artifact androidSourcesJar

                // Mostly self-explanatory metadata
                pom {
                    name = PUBLISH_ARTIFACT_ID
                    // описание нашей библиотеки
                    description = 'Different animated buttons for android applications'
                    // ссылка на сайт либы или на Github
                    url = 'https://github.com/KiberneticWorm/Awesome-Buttons'
                    licenses {
                        license {
                            name = 'Apache License 2.0'
                            // также вам нужно создать лицензию для своей либы
                            // я скопировал ее из Open Source проекта
                            url = 'https://github.com/KiberneticWorm/Awesome-Buttons/blob/master/LICENSE.txt'
                        }
                    }
                    developers {
                        developer {
                        		// далее указывает id, имя и почту разработчика
                            id = 'Twilly'
                            name = 'Dmitry Tsyvtsyn'
                            email = 'dmitry.tehnicov@gmail.com'
                        }
                        
                    }

                    scm {
                    		// информация о библиотеки на Github'е
                        connection = 'scm:github.com/KiberneticWorm/Awesome-Buttons.git'
                        developerConnection = 'scm:git:ssh://github.com/KiberneticWorm/Awesome-Buttons.git'
                        url = 'https://github.com/KiberneticWorm/Awesome-Buttons/tree/main'
                    }
                }
            }
        }
    }
}

signing {
    useInMemoryPgpKeys(
            rootProject.ext["signing.keyId"],
            rootProject.ext["signing.key"],
            rootProject.ext["signing.password"],
    )
    sign publishing.publications
}

Также нам нужно добавить скрипт publish-root.gradle:

// мы укажем все эти переменные в файле local.properties
ext["signing.keyId"] = ''
ext["signing.password"] = ''
ext["signing.key"] = ''
ext["ossrhUsername"] = ''
ext["ossrhPassword"] = ''
ext["sonatypeStagingProfileId"] = ''

File secretPropsFile = project.rootProject.file('local.properties')
if (secretPropsFile.exists()) {
    // Read local.properties file first if it exists
    Properties p = new Properties()
    new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
    p.each { name, value -> ext[name] = value }
} else {
    // Use system environment variables
    ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
    ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
    ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
    ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
    ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
    ext["signing.key"] = System.getenv('SIGNING_KEY')
}

nexusPublishing {
    repositories {
        sonatype {
            stagingProfileId = sonatypeStagingProfileId
            username = ossrhUsername
            password = ossrhPassword
            nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
            snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
        }
    }
}

Теперь нам нужно добавить maven-publish плагин и некоторые константы в начало build.gradle файла нашего модуля:


plugins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'maven-publish'
}

// чтобы пользователь смог добавить нашу библиотеку 
// мы должны указать group id, artifact id и ее версию
ext {
		// group id
    PUBLISH_GROUP_ID = 'io.github.kiberneticworm'
    // текущая версия библиотеки
    PUBLISH_VERSION = '1.0.1'
    // artifact id библиотеки
    PUBLISH_ARTIFACT_ID = 'awesome-buttons'
}

apply from: "${rootProject.projectDir}/scripts/publish-module.gradle"

// ...

И самый важный момент, файл local.properties:

// добавьте следующие константы

// ID ключа, последние 8 символов
signing.keyId=
// пароль, который вы задали при создании ключа
signing.password=
// имя пользователя JIRA Sonatype
ossrhUsername=Twilly
// пароль
ossrhPassword=
// вернемся чуть позже
sonatypeStagingProfileId=
// приватный ключ, который вы получили ранее
signing.key=

Публикация

После основных настроек, зайдите на Nexus Repository Manager и войдите под учетными данными JIRA Sonatype.

Далее переходим во вкладку Staging Profiles и выбираем необходимый профиль.

Копируем sonatypeStagingProfileId из адреса сайта и указываем его в local.properties файле:

Переходим к публикации.

// укажите свой модуль библиотеки
./gradlew awesome-buttons:publishReleasePublicationToSonatypeRepository

Далее у вас есть два варианта зарелизить либу: вручную через графический интерфейс или с помощью gradle задачи.

Первый

Зайдите на сайт Nexus Repository Manager , перейдите во вкладку Staging Repositories и выберите необходимый репозиторий.

Чтобы выпустить релиз библиотеки нужно воспользоваться двумя командами close и release.

Для отмены релиза юзайте drop

Второй

Выполните команду, которая сама закроет и зарелизит вашу либу:

./gradlew closeAndReleaseSonatypeStagingRepository

Заключение

Вы уже убедились, что процесс публикации Andorid либы весьма затратный и рутинный.

Я постарался изложить только основные моменты. Возможно у вас возникнут какие-либо ошибки, которые вам придется самим решать ну и конечно же гуглить!

Такова жизнь прогера: постоянно гуглить. :)

Надеюсь, что статья оказалась вам полезной.

Желаю всем хорошего кода и побольше успешных релизов! :)

Комментарии (8)


  1. Reformat
    26.10.2021 10:23
    +4

    ...а если ваша библиотека open-source, то можно просто использовать JitPack.
    Публикуете GitHub-релиз с исходниками и она сразу подцепляется из любого проекта.
    Шаманизма с ключами не требуется. Например так.


    1. Firsto
      26.10.2021 10:50
      +2

      Поддерживаю, такой способ гораздо удобнее.


    1. Kerrigan
      26.10.2021 14:45

      После чего jitpack отваливается в самый ответственный момент(вы же наверняка на бесплатной версии сидите). Или отдает 403 при попытке выкачать ваш AAR. Или удаляет билд и нужно сходить к ним на сайт и протыкать кнопку, чтобы заново собрало. Для своих проектов это еще как-то сойдет, но для долгих - я бы не советовал.


      1. Reformat
        26.10.2021 14:48

        И сколько раз у вас отваливался JitPack?


        1. Kerrigan
          26.10.2021 14:51

          Раза 4 было, прямо сейчас одна из зависимостей 403


          1. Reformat
            27.10.2021 04:14

            У меня совсем другой опыт, за все время пользования полет нормальный... Некоторые достаточно востребованные проекты тоже хостятся на JitPack, kotlin-telegram например, или KWeb.


    1. osipxd
      27.10.2021 17:10

      Для старта покатит. Но в Maven Central публикация получается качественней как раз из-за того что Maven Central требует, чтобы были:
      - исходники
      - JavaDoc
      - подпись
      - данные о библиотеке и о разработчике в pom.xml

      Хотя сам процесс публикации не удобный, это да.


  1. aleksandy
    27.10.2021 13:21

    1. Светить приватный ключ в интернете, пусть и на скриншоте - так себе затея. :)

    2. Что-то как-то заморочено всё. Когда я такое проделывал, достаточно было после закрытия тикета и прописывания ключей вызвать mvn deploy.