- PVSM.RU - https://www.pvsm.ru -

Публикация артефакта в Maven Central через Sonatype OSS Repository Hosting Service

На написание этой статьи меня сподвигла некоторая разрозненность источников информации по такой
важной, на мой взгляд, теме, как публикация своих артефактов в Maven Central. Конечно, следует
отдать должное сотрудникам Sonatype: их официальный гайд (ссылка в конце статьи) достаточно полно
описывает весь процесс. Но в нём нет некоторых неочевидных тонкостей (вроде проблемы хранения
паролей), и он сам выглядит немного неряшливо. На русском же языке на эту тему ресурсов я не нашёл в
принципе. Лично для меня это не страшно, но многих это может остановить.

Наверное, большинство Java-программистов знает, что такое Maven Central. Для тех, кто не знает:
Maven Central — это огромный репозиторий библиотек для платформы JVM. Кто угодно может выложить
туда библиотеку, если её лицензия позволяет публичное распространение. После этого эту библиотеку
можно указывать как зависимость в системе сборки проекта, такой, как Maven. Тогда при сборке проекта
эта система автоматически скачает библиотеку и скомпилирует ваш проект с её использованием. На мой
взгляд, именно наличие Maven Central есть одна из ключевых причин (если не самая главная) успеха
Java как платформы. Я не знаю более ни одного репозитория, который сравнится с Central по количеству
артефактов и удобству в использовании; единственное, что приходит на ум — это CPAN, Comprehensive
Perl Archive Network, или Haskell'овый Cabal, но это всё же не совсем то.

Кто угодно может выложить в Central библиотеку. Можно даже выкладывать не собственные библиотеки, а
чужие, которые никто ещё не сподобился выложить туда, но только если лицензия библиотеки позволяет
такое распространение. Неделю назад я написал одну библиотеку и решил выложить её в Central. Как
выяснилось, это несколько нетривиальный процесс на первый взгляд, хотя в целом ничего сверхсложного
в нём нет.

Изначально, насколько я понял из остатков документации по этой теме, артефакты в Maven Central
забирались их сервером с помощью rsync. Однако это было признано неудачным решением, и теперь Maven
Central пополняется из нескольких больших доверенных хранилищ артефактов. То, которое нас
интересует, называется Sonatype OSS Repository Hosting Service. Это проект компании Sonatype,
предоставляющий любому желающему возможность поместить свою библиотеку в общий доступ. Сервера Maven
Central регулярно синхронизируются с OSSRH, и те артефакты, которые пользователь помечает как
готовые к релизу, загружаются в центральное хранилище.

Вкратце процедура загрузки своего артефакта в Central выглядит так: нужно зарегистрироваться на
Sonatype JIRA и оставить тикет на открытие репозитория; затем, когда репозиторий откроют, нужно будет
загрузить в него артефакты (вручную или с помощью особой конфигурации системы сборки), а затем нужно
указать, что эти артефакты следует залить в Central.

Репозиториев Sonatype предоставляет два, Snapshots и Staging. Репозиторий Snapshots предназначен для
так называемых снэпшотов, фактически, сборок непосредственно из основной ветки разработки в
системе контроля версий. Репозиторий Staging предназначен для релизов артефактов — тех версий,
которые не будут более меняться. Staging так называется, потому что артефакты из него после проверки
работоспособности нужно «продвинуть» (promote) в Maven Central. Через некоторое время Maven Central
синхронизируется с Sonatype OSSRH, и ваши артефакты появятся в Central.

Рассмотрим все шаги по пунктам.

Создаём тикет на Sonatype JIRA

Первым шагом будет открытие репозитория, в который будут загружаться артефакты для синхронизации с
Central. Это делается через систему тикетов в Sonatype JIRA. Ссылка: http://issues.sonatype.org/ [1].

Сначала там необходимо зарегистрироваться. На открывшейся странице по указанной ссылке нажмите Sign Up. Откроется стандартная форма регистрации. Заполните и отправьте её. Следует учесть, однако, что
в поле Full Name нужно ввести именно полное имя, вроде «Vasiliy Pupkin», в два слова через
пробел. Официальный гайд утверждает, что это из-за проблем с интеграцией между JIRA и нечто под
названием Crowd.

После заполнения и отправки формы вы автоматически окажетесь залогинены в систему. На главной
странице в списке проектов слева можно найти такую ссылку:
https://issues.sonatype.org/browse/OSSRH [2]. Это тот раздел, в котором нужно создать тикет на открытие
репозитория. Для создания тикета нажимаем кнопку New Project. Поля следует заполнить примерно так:
Публикация артефакта в Maven Central через Sonatype OSS Repository Hosting Service [3]

Важный момент: group id я указал общее — org.bitbucket.googolplex, а не
org.bitbucket.googolplex.devourer. Это делается с прицелом на будущее: вдруг вы захотите
опубликовать ещё один свой проект? В таком случае не придётся заводить дополнительных тикетов,
глобальный префикс для репозиториев уже будет принадлежать вам. В принципе, люди, которые работают
над этими тикетами, достаточно адекватные и опытные, поэтому они могут вам поменять group id на
более общий самостоятельно.

В официальной справке [4] говорится, что group id нужно выбирать таким образом, чтобы оно обозначало
домен, контролируемый вами. Это может быть ваш сайт или раздел на сервисе контроля версий вроде
Github или Bitbucket (как у меня, в примере выше). Перед тем, как создавать для вас репозиторий,
имя пакета проверят и напишут, если с ним какие-то проблемы (например, соответствующий сайт
принадлежит не вам).

Через некоторое время сотрудник Sonatype создаст набор репозиториев для вас и закроет тикет. Мне
повезло, мой тикет закрыли практически сразу после его создания. После этого можно будет загружать
артефакты в staging-репозиторий, а оттуда их можно «продвигать» (promote) в Central. Однако перед
тем, как загружать артефакты в репозиторий, их необходимо создать. Для этого нужно правильным
образом настроить мавен, то есть файл pom.xml вашего проекта и, возможно, системный файл
настроек мавена settings.xml.

Создаём и готовим к выгрузке артефакты

Сейчас есть множество систем сборки, умеющих создавать стандартные мавеновские артефакты: Maven,
Buildr, SBT, Gradle, Leiningen и другие. Инструкции ниже верны для Maven версии 3. В статье на вики
Sonatype (ссылка в конце этой статьи) есть инструкции по использованию других систем сборки.

Сущность понятия «артефакт» в контексте мавена очень проста — это всего лишь обычный jar-файл с
дополнительной метаинформацией. Этим описанием артефакта служит так называемый POM, Project Object
Model, представленный файлом pom.xml. Я не буду здесь описывать формат полностью, а только укажу
те части POM, которые должны быть в наличии для успешной загрузки артефакта в Maven Central.

Итак, согласно официальной документации Sonatype, в pom.xml должны присутствовать следующие
секции:

  • <modelVersion>
  • <groupId>
  • <artifactId>
  • <version>
  • <packaging>
  • <name>
  • <description>
  • <url>
  • <licenses>
  • <scm><url>
  • <scm><connection>
  • <developers>

Также, если packaging артефакта равен jar, и артефакт содержит классы, то помимо основного
артефакта следует загрузить -sources.jar и -javadoc.jar. Если это невозможно, например, если не
позволяет лицензия или если ваш проект написан на Scala, то нужно создать фейковые -sources.jar и
-javadoc.jar с README-файлом в них и воспользоваться ими.

Кроме того, в pom.xml не должно быть секций <repository> или <pluginRepository>, потому что
такая конфигурация может сломать сборку у пользователей вашего артефакта, если указанные репозитории
лягут.

Необходимо также подписать артефакты при помощи GnuPG, и соответствующий публичный ключ должен быть
загружен на сервер ключей hkp://pool.sks-keyservers.net/. Про настройку GnuPG чуть ниже.

Вот пример базового pom.xml с необходимой структурой:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.bitbucket.googolplex.devourer</groupId>
    <artifactId>devourer</artifactId>
    <version>0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Devourer</name>
    <description>Feeds on XML and produces objects</description>
    <url>http://bitbucket.org/googolplex/devourer</url>

    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
            <distribution>repo</distribution>
        </license>
    </licenses>

    <scm>
        <url>https://bitbucket.org/googolplex/devourer</url>
        <connection>scm:hg:https://bitbucket.org/googolplex/devourer</connection>
    </scm>

    <developers>
        <developer>
            <id>owner</id>
            <name>Vladimir Matveev</name>
            <email>dpx.infinity@gmail.com</email>
            <timezone>UTC+4</timezone>
        </developer>
    </developers>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>14.0-rc3</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

В принципе, такого pom.xml достаточно, чтобы загружать артефакты в репозиторий. Артефакты тогда
надо будет подписывать вручную и вручную же загружать их в staging-репозиторий. Однако, мавен был
придуман для того, чтобы автоматизировать рутинные операции, и деплой артефактов в репозиторий
содержится в их числе.

Чтобы мавен сам загружал артефакты в репозиторий и подписывал их, нужно добавить и сконфигурировать
несколько плагинов. Однако, перед этим настроим GnuPG.

Настройка GnuPG для подписи артефактов

GnuPG (GNU Privacy Guard) — это альтернативная реализация известного пакета программ для
криптографии PGP, полностью совместимая со стандартом OpenPGP. С помощью GnuPG можно создавать ключи
для асимметричного и симметричного шифрования данных, а также проводить само шифрование многими
алгоритмами. Также с помощью GnuPG можно подписывать данные. Именно этот функционал нам интересен.

Установка GnuPG зависит от системы. В линуксе всё очень просто. Обычно GnuPG поставляется в
стандартном репозитории и, вполне вероятно, уже установлен у вас. Если нет, то нужно попросить
менеджер пакетов его установить. В Archlinux, например, это делается так:

$ sudo pacman -S gnupg

После установки в PATH появится программа gpg. Этого достаточно для мавена.

Под Windows установка немного сложнее. GnuPG предоставляет бинарники, собранные для Windows, но там
есть только относительно старые версии. На мой взгляд, лучшим вариантом будет воспользоваться
gpg4win. Это сборка GnuPG под Windows с несколькими дополнительными программами, например,
графическим менеджером ключей. Для мавена это, в общем-то, не нужно, но может быть полезно вам. Сайт
gpg4win: http://www.gpg4win.org/ [5]. Там можно найти установщик. При установке он должен автоматически
добавить в системный PATH путь к каталогу, где лежит gpg.exe — то, что нам и нужно.

После установки GnuPG открываем терминал и пишем в нём:

$ gpg --gen-key

GnuPG запросит у вас следующие параметры:

  1. Алгоритм ключа — по умолчанию у вас, скорее всего, будет RSA and RSA, это наилучший вариант.
  2. Размер ключа — по умолчанию должно быть 2048 бит, этого вполне достаточно.
  3. Срок действия ключа — по умолчанию ключ действует неограниченно долго; вы можете установить
    срок действия как дополнительную меру безопасности. После окончания срока действия ключ можно
    будет продлить, если вы не забыли пароль к ключу.
  4. Имя и E-Mail — ваши имя и электронная почта. Желательно всё же указать здесь реальные
    значения.
  5. Комментарий — необязательное поле (не знаю, зачем оно нужно), оставьте его пустым.
  6. Пароль — здесь нужно ввести пароль для защиты приватного ключа. В зависимости от системы
    пароль вам предложат ввести по-разному: может появиться графическое окно (gpg4win или GnuPG,
    запущенный из под графической оболочки линукса), или возникнет предложение ввести пароль в
    терминале. Этот пароль будет запрашиваться всегда, когда вам понадобится использовать приватный
    ключ, например, для подписи или обновления времени действия ключа.

После ввода всех этих параметров GnuPG попросит подёргать мышкой или попечатать что-нибудь на
клавиатуре, чтобы получить энтропию для генерации ключа. Будьте с этим осторожны, если вы работаете
на сервере через SSH: GnuPG может зависнуть очень надолго в ожидании энтропии от устройств, а через
SSH мышь или клавиатуру не подёргаешь.

Затем GnuPG сообщит об успешной генерации ключей и выведет их отпечаток. Ещё раз проверить наличие
ключа можно, введя команду gpg --list-keys:

$ gpg --list-keys
C:/Users/vpupkin/AppData/Roaming/gnupg/pubring.gpg
----------------------------------------------------
pub   2048R/1A89FE67 2013-02-26
uid                  Vasiliy Pupkin <vp@example.com>
sub   2048R/D9725D51 2013-02-26

Теперь вы можете подписывать файлы (да и вообще любые данные) с помощью GnuPG. Остался ещё один
момент: нужно загрузить ваш публичный ключ на сервер ключей hkp://pool.sks-keyservers.net. Для
этого нужно ввести следующую команду:

$ gpg --keyserver hkp://pool.sks-keyservers.net --send-keys 1A89FE67

Аргументом к параметру --send-keys указан короткий отпечаток ключа, который можно взять из вывода
команды gpg --list-keys. Через некоторое время GnuPG завершит работу, что будет означать, что ключ
опубликован.

Теперь, если вы не хотите подписывать артефакты и загружать их в репозиторий вручную, нужно
настроить мавен, чтобы он делал это за вас.

Настройка мавена для автоматической подписи и загрузки артефактов в snapshot- и staging-репозитории

Большая часть конфигурации мавена делается из pom.xml настройкой соответствующих плагинов. Однако,
часть конфигурации следует добавить в settings.xml, системном файле настроек мавена. Обычно этот
файл лежит в $HOME/.m2/settings.xml (%HOME%.m2settings.xml в Windows) и предназначен для
специфичных для пользователя настроек, вроде паролей или репозиториев.

Сначала нужно сделать так, чтобы ваш pom.xml наследовался от артефакта, предоставленного Sonatype
специально для упрощения выгрузки артефактов к ним на сервера. Для этого нужно добавить следующий
кусок в ваш pom.xml:

<parent>
    <groupId>org.sonatype.oss</groupId>
    <artifactId>oss-parent</artifactId>
    <version>7</version>
</parent>

Насколько я понимаю (сам я ещё не пробовал), если вы собираетесь загружать сложный многомодульный
проект, который уже использует наследование POM, то нужно сделать oss-parent предком всех тех
артефактов, которые предков не имеют, то есть, корней всех иерархий в проекте. Обычно такой корень
один, но всё же.

Вот список необходимых плагинов:

  • maven-source-plugin
  • maven-javadoc-plugin
  • maven-gpg-plugin

Также нужен ещё maven-release-plugin, но он добавляется автоматически с нужной конфигурацией через
наследование от oss-parent.

Самое простое — настройка maven-source-plugin и maven-javadoc-plugin. Достаточно добавить
следующие секции в pom.xml:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.2.1</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>package</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>2.9</version>
            <executions>
                <execution>
                    <id>attach-javadocs</id>
                    <phase>package</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Здесь мы указали, что на фазе жизненного цикла package нужно создать jar-файлы с исходниками и с
документацией. Остальные плагины, maven-release-plugin и maven-gpg-plugin, автоматически
подхватят эти сгенерированные артефакты, никакой дополнительной конфигурации не потребуется.

Далее, настроим плагин для релиза артефактов, maven-release-plugin. Как я уже сказал, этот плагин
автоматически конфигурируется в артефакте oss-parent, от которого должны наследоваться наши
артефакты. Также в этом артефакте конфигурируются адреса репозиториев. maven-release-plugin будет
использовать именно их для загрузки артефактов. Но, очевидно, просто так загрузить артефакты в
репозиторий нельзя, нужно себя идентифицировать, то есть указать имя и пароль.

Имя и пароль указываются в settings.xml. Окончательный вариант этого файла должен выглядеть
примерно так:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                        http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>sonatype-nexus-snapshots</id>
            <username>vpupkin</username>
            <password>password</password>
        </server>

        <server>
            <id>sonatype-nexus-staging</id>
            <username>vpupkin</username>
            <password>password</password>
        </server>

        <server>
            <id>bitbucket.org</id>
            <username>vpupkin</username>
            <password>password</password>
        </server>
    </servers>
</settings>

Идентификаторы репозиториев sonatype-nexus-snapshots и sonatype-nexus-staging определены в
oss-parent, и мавен автоматически возьмёт имя и пароль из соответствующих секций settings.xml
для загрузки артефактов в эти репозитории. Имя и пароль здесь должны быть указаны те, что были
использованы при регистрации на Sonatype JIRA.

Также я ещё указал здесь сервер с идентификатором bitbucket.org. Это нужно для плагина
maven-scm-plugin, который используется плагином maven-release-plugin для создания в системе
контроля версий тегов, соответствующих релизу. Для того, чтобы это работало, нужно указать
дополнительный адрес в блоке <scm> в pom.xml:

<scm>
    <url>https://bitbucket.org/googolplex/devourer</url>
    <connection>scm:hg:https://bitbucket.org/googolplex/devourer</connection>
    <developerConnection>scm:hg:https://bitbucket.org/googolplex/devourer</developerConnection>
</scm>

Новый адрес указан в элементе developerConnection. Здесь он совпадает с адресом в connection,
но, вообще говоря, они могут быть разными: connection означает адрес на read-only доступ к коду, а
developerConnection — на read-write. Как раз для него мы и добавляли логин и пароль к
bitbucket.org в settings.xml. maven-scm-plugin сам распознает, что этот логин и пароль
относятся к серверу, который указан в <scm><developerConnection>.

Для работы maven-scm-plugin бинарник вашей системы контроля версий должен находиться в
PATH. Например, в моём примере это должен быть hg или hg.exe. Поэтому перед использованием
maven-release-plugin вам потребуется настроить вашу VCS и операционную систему соответствующим
образом. На этом останавливаться не будет: под линуксом всё очень просто, а под Windows установщики
обычно достаточно умные, чтобы добавить пути в PATH.

Теперь нужно настроить maven-gpg-plugin. Вообще говоря, подписывать нужно только релизные версии
артефактов, то есть те, которые попадут в Maven Central. Поэтому имеет смысл настроить вызов этого
плагина только тогда, когда запускается команда для создания релизного артефакта. Мавен, точнее,
maven-release-plugin, предоставляет такую возможность.

Воспользуемся такой фичей мавена, как профили. Профили позволяют динамически включать и отключать
различные части конфигурации мавена по необходимости, в том числе и плагины. Профили можно объявлять
как в pom.xml, так и в settings.xml. Объявим профиль для вызова maven-gpg-plugin прямо в
pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    ...
    <profiles>
        <profile>
            <id>sign-artifacts</id>
            <activation>
                <property>
                    <name>performRelease</name>
                    <value>true</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.4</version>
                        <configuration>
                            <passphrase>${gpg.passphrase}</passphrase>
                        </configuration>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Здесь объявляется новый профиль, sign-artifacts, и указывается, что его следует активировать
тогда, когда системное свойство performRelease будет равно true. Это свойство устанавливает
maven-release-plugin, когда запущена его команда на создание релиза. В профиле конфигурируется
maven-gpg-plugin: его следует запускать по достижении фазы жизненного цикла verify. Также
плагину передаётся наш пароль для GPG-ключа.

Пароль этот передаётся через свойство gpg.passphrase. Понятно, что сохранять пароль на приватный
ключ в самом pom.xml несколько неразумно. Вместо этого сконфигурируем его в settings.xml. Для
этого создадим ещё один профиль, но уже внутри settings.xml. Выглядеть это будет так:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                        http://maven.apache.org/xsd/settings-1.0.0.xsd">
    ...
    <profiles>
        <profile>
            <id>gpg-passphrase</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <gpg.passphrase>passphrase</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
</settings>

Здесь мы указываем, что в профиле нужно определить свойство gpg.passphrase со значением, равным
паролю к ключу. Это значение будет подставлено в соответствующее место в конфигурации
maven-gpg-plugin'а. В условиях активации стоит <activeByDefault>, потому что пароль к ключу
понадобится не только во время самого процесса релиза, но и в процессе подготовки к нему, а для
этого никаких отличительных свойств никакими плагинами не устанавливается.

Выгружаем артефакты в репозиторий

В общем, это всё, что нужно сделать, чтобы мавен умел загружать артефакты в репозиторий. Если теперь
вы выполните такую команду в корне проекта:

$ mvn deploy

то через некоторое время в snapshot-репозиторий будет загружена текущая версия ваших артефактов.

К snapshots-репозиторию можно получить доступ извне, например, чтобы пользоваться snapshot-версиями
артефакта. Возможно это сделать, например, по этому адресу:
https://oss.sonatype.org/content/groups/staging [6]. Однако, репозиторий по этой ссылке важен по другой
причине. В него попадают не только снэпшоты и релизы, но и т.н. staged-версии
артефактов. Фактически, это релизные версии, которые ещё не синхронизированы Maven
Central. Stage-пространство нужно для последней проверки, что с вашими артефактами всё в порядке. То
есть, перед релизом артефакта в Central стоит проверить, что, например, проект, использующий ваш
артефакт, компилируется нормально, если он будет зависеть от новой релизной версии. Если возникнут
какие-то проблемы, staged-артефакт можно удалить и создать заново. Как только все проблемы
разрешены, staged-артефакт можно «продвинуть» в Maven Central. С этого момента его изменить уже
будет нельзя.

Рассмотрим поподробнее, как выглядит загрузка артефакта в staging-репозиторий, и что нужно делать
для «продвижения» артефакта в Central.

Поскольку проект уже настроен для релиза артефактов, достаточно выполнить две команды:

$ mvn release:prepare
...
$ mvn release:perform
...

Первая команда подготавливает релиз. Если конкретнее, то она делает следующее:

  • проверяет, что в дереве исходников нет незакоммиченных изменений;
  • проверяет, что у всех артефактов нет зависимостей на snapshot-версии других артефактов;
  • меняет версию у всех артефактов с x-SNAPSHOT на ту, что вы укажете;
  • изменяет информацию о системе контроля версий в pom.xml, чтобы она соответствовала новому пути
    в репозитории для нового релиза (актуально для тех VCS, в которых нет отдельного понятия тегов,
    таких, как SVN);
  • запускает автоматические тесты с изменёнными pom.xml, чтобы проверить, что ничего не сломалось;
  • коммитит изменённые pom.xml;
  • создаёт тег в репозитории для новой версии, запрашивая у вас имя тега;
  • обновляет версию в pom.xml на y-SNAPSHOT (это тоже плагин у вас спросит);
  • коммитит изменённые pom.xml.

Таким образом, после выполнения mvn release:prepare в репозитории образуется два коммита (первый с
обновлением текущей snapshot-версии на релизную, а второй — с релизной на новую snapshot'ную) и
один тег (который будет указывать на тот коммит, в котором присутствует релизная версия).

Другими словами, mvn release:prepare создаёт «точку релиза» в репозитории, фиксируя то состояние
проекта, которое попадёт в Central.

Теперь нужно загрузить артефакт в репозиторий. Для этого служит вторая команда, mvn release:perform. Эта команда выкачивает тег релизной версии из репозитория и собирает его командой
вроде mvn site-deploy. Команда mvn site-deploy в условиях такой конфигурации проекта, которую мы
сделали, загрузит артефакт в staging-репозиторий.

Очевидно, mvn release:perform должна откуда-то брать название тега, который нужно загрузить. Она
может брать тег либо из файла release.properties, который создаётся командой mvn release:prepare, либо из командной строки. После успешной выгрузки артефакта и сопутствующих файлов
в репозиторий, release:perform удалит лишние файлы, созданные release:prepare.

Внимание: при использовании таких VCS, у которых понятие тега не связано с файловой системой
(например, это Mercurial и Git), через командную строку указать название тега, к сожалению,
нельзя. Я вообще не нашёл способа указать тег для mvn release:perform, если перед этим не был
выполнен mvn release:prepare. Если кто-то подскажет, как это сделать, буду очень благодарен.

Таким образом, после выполнения этих команд ваши артефакты будут загружены в staging-репозиторий. Их
можно увидеть через веб-интерфейс системы управления репозиториями Sonatype Nexus на
https://oss.sonatype.org/ [7]. В правом верхнем углу по ссылке Log In нужно войти в систему с тем
логином и паролем, который вы выбрали при регистрации на JIRA. После этого слева в списке появится
группа Build Promotion, а в ней три ссылки. Нужная нам называется Staging Repositories. По
нажатию на неё в центральном окне откроется список staging-репозиториев, то есть, временных хранилищ
для артефактов в состоянии staging. В официальной документации приводятся скриншоты окна, в котором
виден всего лишь один репозиторий, тот, в который мы только что выгрузили артефакт. Однако, по
какой-то причине у меня это выглядит так:
Публикация артефакта в Maven Central через Sonatype OSS Repository Hosting Service [8]

Как мне ответили в тикете на JIRA, наличие этих артефактов не страшно, они не мешаются, и у обычных
пользователей всё равно нет прав на их изменение.

Мой репозиторий наверху. Видно, что он в статусе Closed, но у вас он будет сначала в статусе
Open. Вообще staging-репозиторий может быть в двух состояниях — Open и Closed. В статусе
Open репозиторий находится сразу после выгрузки артефактов. В состояние Closed репозиторий нужно
перевести вручную, с помощью кнопки Close на панели инструментов сверху. При этом Nexus проверит,
что все артефакты в репозитории находится в состоянии, подходящем для Maven Central: присутствуют
корректные цифровые подписи, pom.xml имеет правильный формат и т.д. Если будут какие-то проблемы,
то Nexus сообщит об ошибках, и после исправления проблема артефакты надо будет выгрузить снова. Как
только репозиторий оказывается в статусе Closed, он более недоступен для изменения; именно в таком
виде он попадёт в Maven Central. После закрытия репозитория артефакты становятся доступным в
staging-репозитории: https://oss.sonatype.org/content/groups/staging [6]. Можно воспользоваться им,
чтобы проверить работу артефакта перед тем, как «продвигать» его в Maven Central.

Если вы удовлетворены проверками и считаете, что уже пора бы вашему артефакту попасть в Central, то
нужно выбрать ваш staging-репозиторий и нажать кнопку Release на панели инструментов. Через
некоторое время staging-репозиторий пропадёт из списка. Теперь артефакт попадёт в основной
репозиторий Sonatype OSSRH: https://oss.sonatype.org/content/groups/public [9]. Но раз мы хотим, чтобы
наш артефакт был доступен в Maven Central, то нужно сделать ещё один шаг.

Поскольку вы загружаете свой репозиторий в Maven Central в первый раз, вам нужно будет оставить
комментарий на вашем тикете в Sonatype JIRA о том, что вы «продвинули» свой артефакт, и что нужно
включить синхронизацию с Maven Central:
Публикация артефакта в Maven Central через Sonatype OSS Repository Hosting Service [10]

Сотрудник Sonatype вам ответит через некоторое время (здесь мне опять повезло: мне ответили почти
сразу после того, как я оставил комментарий; похоже, что самое подходящее время для общения на JIRA
— около 12 ночи по Москве), что синхронизация включена. В течение нескольких часов ваш артефакт
будет доступен в Maven Central. Поздравляю! :)

Процедуру с комментарием надо провести только один раз. После этого синхронизация будет включена уже
навсегда, и все артефакты, которые вы загружаете в staging-репозиторий после «продвижения» будут
синхронизированы с Maven Central автоматически.

Замечания по поводу хранения паролей в settings.xml

Возможно, некоторые читатели возмутились, что я использовал в статье хранение паролей в открытом
виде в settings.xml. В этом я с ними соглашусь: хранение паролей плейнтекстом — действительно
неудачная идея. К счастью, создатели мавена об этом также осведомлены. В мавене есть возможность
хранить пароли и в зашифрованном виде.

Использование зашифрованных паролей описано в руководстве по мавену:
http://maven.apache.org/guides/mini/guide-encryption.html [11]. Если вкратце, то зашифрованные пароли
используются следующим образом.

  1. Создаётся мастер-пароль:
    $ mvn --encrypt-master-password your-password
    

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

    {FshtxgrHbMoH+X+v4cwYL9t9QgtwTgoLmfiV6LkPkvg=}
    

    Это значение необходимо сохранить в файле $HOME/.m2/settings-security.xml, вот таким образом:

    <settingsSecurity>
        <master>{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}</master>
    </settingsSecurity>
    

  2. Теперь можно шифровать серверные пароли. Делается это с помощью команды:
    $ mvn --encrypt-password your-password
    

    Пароль также надо указать в командной строке. Результат будет наподобие такого:

    {tly+tTyx46MHPhht/SryJw8+x4n4oWfzgAPZ/B+KFoc=}
    

    Этот пароль можно использовать внутри секции <servers> в settings.xml:

    <settings>
    ...
        <servers>
    ...
            <server>
                <id>my.server</id>
                <username>foo</username>
                <password>{tly+tTyx46MHPhht/SryJw8+x4n4oWfzgAPZ/B+KFoc=}</password>
            </server>
    ...
        </servers>
    ...
    </settings>
    

    Это будет работать для всех плагинов, использующих <servers>: maven-deploy-plugin,
    maven-scm-plugin и т.д. Однако, здесь не обошлось без ложки дёгтя: см. далее.

    Также иногда может быть так, что мавен откажется шифровать пароль с какой-то ошибкой насчёт блока
    шифра. Это не страшно, достаточно перегенерировать мастер-пароль.

Таким образом, для использования паролей в settings.xml будет обязательно нужен
мастер-пароль. Наличия файла settings-security.xml с зашифрованным мастер-паролем внутри
достаточно, чтобы можно было пользоваться зашифрованными паролями в settings.xml. Чтобы сохранить
мастер-пароль в безопасности, файл settings-security.xml с мастер-паролем можно поместить на
съёмный носитель, например, на флешку, а в файле $HOME/.m2/settings-security.xml указать, что
нужно использовать файл, находящийся в другом месте:

<settingsSecurity>
    <relocation>/media/secure-flash/settings-security.xml</relocation>
</settingsSecurity>

Таким образом, пароли в файле settings.xml могут быть надёжно защищены.

К сожалению, здесь не обошлось без проблем. Во-первых, по какой-то причине maven-scm-plugin у меня
не захотел работать с зашифрованными паролями при использовании Mercurial. Из логов мавена было
видно, что он отправляет пароль as-is, не расшифровывая его, и, естественно, получает ошибку. Я не
знаю с чем это связано, и гугление ничего существенного не дало. Я решил эту проблему использованием
SSH-транспорта для Mercurial. Конкретнее, секция <scm> у меня в pom.xml стала выглядеть так:

<scm>
    <url>https://bitbucket.org/googolplex/devourer</url>
    <connection>scm:hg:https://bitbucket.org/googolplex/devourer</connection>
    <developerConnection>scm:hg:ssh://hg@bitbucket.org/googolplex/devourer</developerConnection>
</scm>

Обратите внимание на <developerConnection>. SSH-клиент у меня на машине настроен на использование
ключей для аутентикации, так что такой метод работает вполне удовлетворительно. Вполне возможно, что
с другими VCS проблем не будет.

Ещё одна проблема — это ограниченность применимости зашифрованных паролей. Их можно использовать
только в <servers>, в любом другом месте дешифрация проводиться не будет. Вспомним наш
дополнительный профиль в settings.xml, который содержит пароль на приватный ключ:

<profiles>
    <profile>
        <id>gpg-passphrase</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <gpg.passphrase>passphrase</gpg.passphrase>
        </properties>
    </profile>
</profiles>

Здесь бесполезно заменять пароль зашифрованной версией, так как maven-gpg-plugin не использует
мавеновское шифрование и, судя по открытому два с половиной года назад тикету [12], вообще не может его
использовать (ну по крайней мере это сопряжено с существенными трудностями).

Возможная альтернатива — отключение пароля на ключ (это возможно сделать с помощью GnuPG) и
перемещение «связки ключей» GnuPG на внешний зашифрованный носитель. Для этого нужно немного
исправить settings.xml и pom.xml.

Профиль в settings.xml:

<profiles>
    <profile>
        <id>gpg-passphrase</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <gpg.passphrase/>
            <gpg.home>/media/secure-flash/gnupg</gpg.home>
        </properties>
    </profile>
</profiles>

Настройки maven-gpg-plugin в pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.4</version>
    <configuration>
        <passphrase>${gpg.passphrase}</passphrase>
        <homedir>${gpg.home}</homedir>
    </configuration>
    <executions>
        <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals>
                <goal>sign</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Здесь мы использовали новое свойство, gpg.home, которое указывает на каталог с ключами GnuPG на
внешнем устройстве, а также убрали пароль на ключ.

Ещё один вариант — просто вводить пароль каждый раз, когда мавен его попросит при сборке. Для
этого нужно убрать опцию <passphrase> в свойствах maven-gpg-plugin в pom.xml. В такой
конфигурации, какая приведена в этой статье, вводить пароль придётся два раза: во время mvn release:prepare и во время mvn release:perform.

Если вы пользуетесь Unix-системами, то, наверное, знаете, что SSH-ключи в каталоге пользователя
защищаются с помощью прав на файлы: у ключей обычно стоят права доступа вроде 600, то есть только
для пользователя-владельца файла. В принципе с файлами конфигурации мавена можно поступить так же
— выставить правильные привилегии на файл settings.xml. Это даст бонус к защите ваших паролей.

Ну и последняя возможность — передавать пароль в командной строке: mvn release:prepare -Dgpg.passphrase=passphrase, но это уже будет совсем большая дыра, так, конечно, лучше не делать.

Насчёт ввода паролей открытым текстом при вызове mvn --encrypt{,-master}-password: с этим ничего
не поделаешь, их придётся вводить как аргумент к команде. После этого стоит почистить файл с
историей вашего шелла, например, ~/.zsh_history. Также после редактирования файлов желательно
посмотреть, что ваш редактор не сохранил пароли в кэше, например, ~/.viminfo или в ~-файлах у
емакса.

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

Заключение

Такие репозитории, как Maven Central — это огромное преимущество Java как платформы, и чем больше
в них будет готовых к использованию артефактов, тем лучше будет для всей экосистемы Java. К счастью,
весь процесс публикации библиотеки оказался не таким сложным, как выглядит. Надеюсь, моя статья
помогла внести ещё большую ясность в него. Спасибо за прочтение.

Буду рад указаниям на все ошибки, опечатки и фактические неточности.

Статья подготовлена в Emacs Org Mode, сконвертирована в формат Хабра с помощью Org -> Docbook -> Habr Exporter [13].


Использованные ресурсы

  1. Официальный гайд Sonatype по использованию OSSRH:
    https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide [14]
  2. Гайд Sonatype по использованию GnuPG совместно с мавеном:
    https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven [15]
  3. Документация по мавену и его плагинам: http://maven.apache.org/guides/index.html [16],
    http://maven.apache.org/plugins/index.html [17]
  4. Конечно же, гугл.

Автор: Googolplex

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/maven/28545

Ссылки в тексте:

[1] http://issues.sonatype.org/: http://issues.sonatype.org/

[2] https://issues.sonatype.org/browse/OSSRH: https://issues.sonatype.org/browse/OSSRH

[3] Image: http://habrastorage.org/storage2/c93/4d8/255/c934d82558b10203e72412dd8b55c69d.png

[4] В официальной справке: https://docs.sonatype.org/display/Repository/Choosing+your+Coordinates

[5] http://www.gpg4win.org/: http://www.gpg4win.org/

[6] https://oss.sonatype.org/content/groups/staging: https://oss.sonatype.org/content/groups/staging

[7] https://oss.sonatype.org/: https://oss.sonatype.org/

[8] Image: http://habrastorage.org/storage2/392/9af/46a/3929af46aff95d8424e1e73ff5ed5816.png

[9] https://oss.sonatype.org/content/groups/public: https://oss.sonatype.org/content/groups/public

[10] Image: http://habrastorage.org/storage2/698/9a8/66a/6989a866aa60d9b1ca9063f5c399282f.png

[11] http://maven.apache.org/guides/mini/guide-encryption.html: http://maven.apache.org/guides/mini/guide-encryption.html

[12] тикету: http://jira.codehaus.org/browse/MGPG-31

[13] Org -> Docbook -> Habr Exporter: https://github.com/dpx-infinity/odh-exporter

[14] https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide: https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide

[15] https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven: https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven

[16] http://maven.apache.org/guides/index.html: http://maven.apache.org/guides/index.html

[17] http://maven.apache.org/plugins/index.html: http://maven.apache.org/plugins/index.html

[18] Источник: http://habrahabr.ru/post/171493/