Публикация Java библиотеки в Maven Central

Публикация артефакта библиотеки в Maven Central через Sonatype OSSHR.

· 11 минуты на чтение

Вы написали библиотеку на Java и теперь хотите сделать ее общедоступной? Самый простой способ распространения библиотек – это загрузить ее в Maven Central.

Если вы думаете, что можно просто зарегистрироваться в Maven Central и загрузить свой шедевр через форму загрузки, то вы ошибаетесь. Придется пройти небольшой квест.

Помимо Maven Central есть и другие Nexus хранилища – Open Source Software Repository Hosting Service (OSSRH). Крупнейшим является Sonatype OSSRH.

Sonatype OSSRH синхронизируется с Maven Central. Поэтому в этой статье мы разберемся, как зарегистрироваться в Sonatype OSSRH и настроить Maven pom для деплоя в Sonatype OSSRH.

Публикация Java приложения в личный Nexus
Рассказываю, как опубликовать свой артефакт в корпоративный или личный Nexus Sonatype
Спонсор поста

Регистрируем groupId

Зависимость в maven централ выглядит следующим образом.

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.1.1</version>
</dependency>

groupId и artifactId составляют идентификатор вашей библиотеки. groupId - это идентификатор группы проекта. Как правило, он уникален среди организаций и представляет собой доменное имя наоборот. artifactId - это идентификатор проекта в группе проектов.

Чтобы иметь возможность загружать библиотеки, необходимо купить доменное имя, которое будет вашим groupId.

😇
Если вы впервые покупаете там домен, то можете воспользоваться промо-кодом на скидку 5% в REG.RU: E403-2760-B801-2B3B

И так мы купили домен, теперь необходимо подать заявку на регистрацию groupId и подтвердить свои права на домен. Делается это через issues в Jira Sonatype.

Поддомены отдельно регистрировать не надо. После регистрации основного домена, вы сможете использовать и его поддомены.

Тема: Publish rights for domain.name
Описание: I would like to publish my name.domain artifacts. The rights to the domain belong to me.
Group Id: ru.upagge
Project URL: Ссылка на ваш проект в GitLab или GitHub
SCM url: Тоже ссылка на ваш проект, но добавьте в конце .git
Already Synced to Central: No

Через какое-то время в ваше issue пришлют инструкцию, как подтвердить владение доменом. Мне прислали через минуту, скорее всего это автоматическое сообщение.

Пойдем самым быстрым путем - добавим TXT запись для домена. Сделать это можно у регистратора домена.

  • Тип записи: TXT
  • Subdomain: @
  • Text: Ссылка на тикет в Jira

После этого ждем пока DNS записи обновятся. У каждого регистратора это занимает разное время. У REG.RU записи обновились за 15 минут.

После того, как вы убедились, что запись появилась, возвращаемся в тикет. Оставляем там короткий комментарий: done.

Буквально через 10 минут приходит оповещение, что все успешно.

Создание GPG ключа

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

Перед настройкой .pom необходимо создать GPG ключ для подписи релиза.

gpg --full-gen-key

gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Выберите тип ключа:
   (1) RSA и RSA (по умолчанию)
   (2) DSA и Elgamal
   (3) DSA (только для подписи)
   (4) RSA (только для подписи)
Ваш выбор? 1
длина ключей RSA может быть от 1024 до 4096.
Какой размер ключа Вам необходим? (3072) 2048
Запрошенный размер ключа - 2048 бит
Выберите срок действия ключа.
         0 = не ограничен
        = срок действия ключа - n дней
      w = срок действия ключа - n недель
      m = срок действия ключа - n месяцев
      y = срок действия ключа - n лет
Срок действия ключа? (0) 0
Срок действия ключа не ограничен
Все верно? (y/N) y

GnuPG должен составить идентификатор пользователя для идентификации ключа.

Ваше полное имя: Struchkov Mark
Адрес электронной почты: example@upagge.ru
Примечание:
Вы выбрали следующий идентификатор пользователя:
    "Struchkov Mark "

Сменить (N)Имя, (C)Примечание, (E)Адрес; (O)Принять/(Q)Выход? O
Необходимо получить много случайных чисел. Желательно, чтобы Вы
в процессе генерации выполняли какие-то другие действия (печать
на клавиатуре, движения мыши, обращения к дискам); это даст генератору
случайных чисел больше возможностей получить достаточное количество энтропии.
Необходимо получить много случайных чисел. Желательно, чтобы Вы
в процессе генерации выполняли какие-то другие действия (печать
на клавиатуре, движения мыши, обращения к дискам); это даст генератору
случайных чисел больше возможностей получить достаточное количество энтропии.
gpg: ключ C7F6E84396D004C4 помечен как абсолютно доверенный
gpg: сертификат отзыва записан в '/home/upagge/.gnupg/openpgp-revocs.d/736AD6032D072F9E452911D5C7F6E84396D004C4.rev'.
открытый и секретный ключи созданы и подписаны.

pub   rsa2048 2021-04-02 [SC]
      736AD6032D072F9E452911D5C7F6E84396D004C4
uid                      Struchkov Mark 
sub   rsa2048 2021-04-02 [E]
  • Тип ключа: RSA и RSA
  • Размер ключа: 2048
  • Срок действия: неограничен

Публикация ключа

Отправьте свой ключ на сервер GPG:

gpg --keyserver keyserver.ubuntu.com --send-keys 736AD6032D072F9E452911D5C7F6E84396D004C4
⚠️
Не волнуйтесь, эта команда отправит только публичный ключ.
Если через консоль ваш ключ не отправляется, то вы можете загрузить его вручную на сайт http://keyserver.ubuntu.com.

Чтобы получить публичный ключ в нужном формате, используйте команду:

gpg --armor --export 736AD6032D072F9E452911D5C7F6E84396D004C4

Настройка settings.xml

Сначала перейдем в глобальные настройки Maven в папке .m2/settings.xml.

Необходимо указать логин и пароль от OSSRH. Также добавим профиль мавена release, в который укажем данные GPG ключа.  gpg.keyname в данном случае будет 736AD6032D072F9E452911D5C7F6E84396D004C4.

<settings>
    <servers>
        <server>
            <id>ossrh</id>
            <username>your-jira-id</username>
            <password>your-jira-pwd</password>
        </server>
    </servers>
    <profiles>
        <profile>
            <id>ossrh</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <gpg.keyname>PGP_KEY</gpg.keyname>
                <gpg.passphrase>YOU_PASSWORD</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
</settings>

После сохранения файла settings.xml перезапустите Idea, чтобы новый профиль появился в списке.

Переходим в pom.xml вашего приложения. Укажем название, описание и ссылку на проект.

// ... ... ... ... ...

<name>uPagge Utils</name>
<description>An example to demonstrate the ability to publish to Maven Central</description>
<url>https://blog.upagge.ru/posts/guide/2021/deploy-to-maven-central/</url>

// ... ... ... ... ...

Эта информация будет использоваться в описании компонента в Maven Central.

Обязательно необходимо указать лицензию, под которой выпускается ваша библиотека. Например:

<licenses>
    <license>
        <name>BSD 3-Clause License</name>
        <url>https://gitlab.com/uPagge/upagge-utils/-/raw/master/LICENSE</url>
    </license>
</licenses>
Вы можете зайти на свой проект на GitHub, нажать кнопку Add File / Create new File. В название файла укажите LICENSE. Вы увидите появление кнопки Choose a license template. Выбирайте понравившуюся

Также указываем информацию о системе контроля версий.

// ... ... ... ... ...

<scm>
    <connection>scm:git:https://gitlab.com/uPagge/upagge-utils.git</connection>
    <url>https://gitlab.com/uPagge/upagge-utils</url>
    <developerConnection>scm:git:https://gitlab.com/uPagge/upagge-utils.git</developerConnection>
</scm>

// ... ... ... ... ...

После этого добавляем ссылки на репозитории

// ... ... ... ... ...

<distributionManagement>
    <snapshotRepository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
    </snapshotRepository>
</distributionManagement>

// ... ... ... ... ...
С февраля 2021 года все новые проекты загружаются на https://s01.oss.sonatype.org/

Также обязательно нужно указать разработчиков:

// ... ... ... ... ...

<developers>
    <developer>
        <id>uPagge</id>
        <name>Struchkov Mark</name>
        <url>https://struchkov.dev</url>
    </developer>
</developers>

// ... ... ... ... ...

Плагины для публикации

Для публикации будем использовать плагин nexus-staging-maven-plugin. А чтобы случайно не запустить публикацию, мы создадим отдельный maven профиль – release.

Но сначала в проперти укажем версии необходимых плагинов.

<properties>
	... ... ... ... ...
    
	<plugin.nexus.staging.ver>1.6.12</plugin.nexus.staging.ver>
    <plugin.maven.source.ver>3.2.1</plugin.maven.source.ver>
    <plugin.maven.javadoc.ver>3.3.1</plugin.maven.javadoc.ver>
    <plugin.maven.gpg.ver>3.0.1</plugin.maven.gpg.ver>
</properties>

Добавляем конфигурацию для плагинов:

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.sonatype.plugins</groupId>
                <artifactId>nexus-staging-maven-plugin</artifactId>
                <version>${plugin.nexus.staging.ver}</version>
                <extensions>true</extensions>
                <configuration>
                    <serverId>ossrh</serverId>
                    <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>${plugin.maven.source.ver}</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>${plugin.maven.javadoc.ver}</version>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-gpg-plugin</artifactId>
                <version>${plugin.maven.gpg.ver}</version>
                <executions>
                    <execution>
                        <id>sign-artifacts</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <gpgArguments>
                        <gpgArgument>--pinentry-mode</gpgArgument>
                        <gpgArgument>loopback</gpgArgument>
                    </gpgArguments>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>

    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>17</source>
                <target>17</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Опционально вы можете использовать плагины maven-source-plugin и maven-javadoc-plugin используются для генерации исходников и javadoc соответсвенно. Сгенерированные ими jar будут также загружены в maven central.

Плагин maven-gpg-plugin используется для финальной подписи ваших jar файлов. Его использование обязательно.

Теперь в pom.xml добавляем новый профиль release:

<profiles>
    <profile>
        <id>release</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.sonatype.plugins</groupId>
                    <artifactId>nexus-staging-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Все, можно опубликовывать. Делается это просто. Активируйте профиля, которые мы создали ранее и запустите команду мавена:

mvn clean deploy
// ... ... ... ... ...

[INFO]  * Upload of locally staged artifacts finished.
[INFO]  * Closing staging repository with ID "ruupagge-1003".

Waiting for operation to complete...
...

[INFO] Remote staged 1 repositories, finished with success.
[INFO] Remote staging repositories are being released...

Waiting for operation to complete...
...

[INFO] Remote staging repositories released.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  34.159 s
[INFO] Finished at: 2021-04-02T19:49:31+03:00
[INFO] ------------------------------------------------------------------------

Переходим в и воспользовавшись поиском находим нашу библиотеку в Sonatype OSSHR.

Теперь возвращаемся в наш изначальный тикет и пишем, что все готово и библиотека загружена. Они включат автоматическую синхронизацию. Это надо делать только в первый раз, дальше все ваши библиотеки будут синхронизироваться автоматически.

Через 8-24 часа переходим в Maven Central и через поиск находим нашу либу.

🎉🎉🎉 Успех! Теперь любой разработчик сможет воспользоваться вашей библиотекой.

Публикация с помощью GitLab CI

Каждый раз публиковать библиотеку запуская команду мавена с компьютера не удобно, реализуем автоматическую публикацию при помощи GitLab CI.

Мы сделаем так, чтобы при каждом новом git теге формата release-* запускалась сборка и публикация в Maven Central. Таким образом каждый ваш релиз будет автоматически попадать в Maven Central.

Настройка GitLab CI CD для Java приложения
На примере Java приложения рассматриваем сборку и деплой на сервер с помощью GitLab CI.
Подробнее о GitLab CI я рассказывал в этой статье 👆

Даше если вы используйте GitHub для хранения кодовой базы, вы можете использовать GitLab CI. GitLab может синхронизировать ваш репозиторий с GitHub, то есть все изменения в GitHub репозитории будут появляться в GitLab. Но изменения будут синхронизироваться с задержкой.

Для этого при создании проекта выберете пункт "Run CI/CD external repository".

Если проект у вас изначально хранился в GitLab, то создавать новый проект не нужно, просто добавьте файл .gitlab-ci.yml в текущий проект по инструкции ниже.

Теперь необходимо защитить теги формата release-*. Для этого переходим в проекте Settings —> Repository —> Protected tags. Вводим release-*. И нажимаем Create wilcard.

Дальше выберите тех, кому можно будет создавать эти теги. При работе в команде это разграничит возможности. Только одобренные разработчики смогут делать релизы и загружать в Maven Central.

Настроим GitLab CI. Для этого в корне проекта создаем новый файл .gitlab-ci.yml:

image: maven:3.8.4-openjdk-11
variables:
  MAVEN_OPTS: "-Dmaven.repo.local=./.m2/repository"

stages:
  - deploy

deploy:
  stage: deploy
  only:
    - /^release-.*$/
  except:
    - branches
  before_script:
    - gpg --pinentry-mode loopback --passphrase $GPG_PASSPHRASE --import $GPG_PRIVATE_KEY
  script:
    - 'mvn --settings $MAVEN_SETTINGS -U -P ossrh,release clean deploy'

Здесь мы указываем, что при обнаружении тега release-* необходимо сначала импортировать GPG ключ, а далее выполнить команду Maven.

⚠️
Я использую образ maven:3.8.4-openjdk-11, если вы используете не Java 11, то вам нужно выбрать свой образ.

Чтобы этот конфиг работал, необходимо создать переменные CICD. Для этого перейдите в Seiitings —> CI/CD —> Variables.

Обратите внимание, что GPG_PRIVATE_KEY и MAVEN_SETTINGS являются типом File.

При создании переменных указывайте параметр Protected. Этим вы сообщаете GitLab CI, что использовать эти переменные можно только в защищенных ветках/тегах.

В переменную GPG_PRIVATE_KEY записываешь приватный ключ GPG. Чтобы его получить выполните в терминале следующую команду:

gpg --armor --export-secret-key 736AD6032D072F9E452911D5C7F6E84396D004C4
-----BEGIN PGP PRIVATE KEY BLOCK-----

lQPGBGGnICAB...PUKn3Q7GBXxg=
=CG7t
-----END PGP PRIVATE KEY BLOCK-----

В переменную GPG_PASSPHRASE просто запишите пароль от GPG ключа.

В переменную MAVEN_SETTINGS укажем такие же настроки, которые прописали в файл .m2/settings.xml.

<settings>
    <servers>
        <server>
            <id>ossrh</id>
            <username>your-jira-login</username>
            <password>your-jira-password</password>
        </server>
    </servers>
    <profiles>
        <profile>
            <id>ossrh</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <gpg.keyname>gpg-keyname</gpg.keyname>
                <gpg.passphrase>gpg-password</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
</settings>

Чтобы запустить автоматическую сборку необходимо добавить тег формата release-* для любого коммита и запушить его.

Теперь все релизы будут автоматически публиковаться в Maven Central.

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇