Je n’avais encore jamais publié une librairie sous Maven Central, afin de la rendre accessible à tous. Il existe plusieurs manières de faire. J’ai choisi le mode classique, préconisé par SonaType, la société qui gère Maven Central et les produits Nexus.
Les différentes versions des librairies open source Java sont déployées sous OSSRH (OSS Repository Hosting). OSSRH utilise Nexus Repository Manager pour gérer les librairies. La plateforme gère tout le cycle de vie des versions d’une librairie
les versions de développements (snapshots) peuvent être déployées
les versions tagguées sont poussées dans un dépôt staging (recette)
vous pouvez ensuite indiquer qu’une version de recette est releasée. La plateforme lance à ce moment plusieurs contrôles de qualité et pousse les binaires sur le dépôt central (Maven Central).
La documentation officielle est assez complète et vous la trouverez ici. Mais comme toute documentation il y a souvent un décalage entre le moment où elle a été décrite et le moment où vous l’appliquez. Sur une version récente de Linux et en utilisant les dernières versions de Gradle vous avez plusieurs points à savoir.
Commençons par le début. La documentation vous demande
de créer un compte sur le Jira de SonaType. Les identifiants de ce compte seront les mêmes que vous utiliserez pour pousser vos artefacts
d’ouvrir une issue dans laquelle vous demandez la création d’un nouveau projet. Les équipes Nexus vont faire un check manuel de votre demande. Ils sont assez réactifs
Même si nous allons utiliser Gradle, nous allons construire un descripteur de projet Maven (pom.xml
), car Maven Central contenait à la base que des ressources pour les projes Maven.
Une fois que les équipes SonaType ont validé votre projet, vous pouvez envoyer votre librairie sur leurs serveurs. Mais différents checks seront faits afin d’assurer une qualité minimale des librairies
vous devez définir les metadata du projet dans un descripteur pom.xml
avec les identifiants (artifactId
, version
, groupId
), le type de packaging, les dépendances transitives et optionelles
en plus de votre artefact vous devez envoyer les sources et la javadoc
tous les artefacts doivent être signés avec GPG/PGP
Regardons comment faire celà avec Gradle. Vous devez importer les plugins suivants pour votre projet Java
apply plugin: 'java'
apply plugin: 'signing'
apply plugin: 'maven'
Le plugin signing
va être utilisé pour signer les artefacts.
Le plugin maven permet de générer un descripteur de projet Maven (pom.xml
) et de publier vos artefacts sur Maven Central
Vous pouvez définir les metadata de votre projet…
group = 'fr.dev-mind'
archivesBaseName = "mockwebserver"
version = rootProject.version
sourceCompatibility = 1.8
puis les tâches pour générer les différents artefacts : jar, sources et javadocs. Les artefacts peuvent contenir un fichier Manifest avec les infos essentielles de votre projet
ext.sharedManifest = manifest {
attributes(
"Implementation-Title": project.name,
"Implementation-Version": version,
"Implementation-Vendor": project.group,
"Bundle-Vendor": project.group
)
}
task sourcesJar(type: Jar) {
from sourceSets.main.allJava
manifest {
from sharedManifest
}
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc
manifest {
from sharedManifest
}
}
javadoc {
source = sourceSets.main.allJava
classpath = configurations.compile
options.linkSource true
options.addBooleanOption('Xdoclint:all,-missing', true)
}
jar {
manifest {
from sharedManifest
}
}
artifacts {
archives javadocJar, sourcesJar
}
Vous devez ensuite paramétrer la signature des artefacts. Dans l’exemple ci dessous, ceci n’est fait que lorsque la tâche uploadArchives
est lancée (tâche permettant de publier vos librairies).
signing {
required { gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
Il ne reste plus qu’à paramétrer cette tâche uploadArchives
avec les informations que l’on veut voir dans le pom.xml
et les dépôts que vous allez utiliser
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
name project.name
packaging 'jar'
description "A scriptable web server for testing HTTP clients"
url 'https://github.com/Dev-Mind/mockwebserver'
scm {
connection 'scm:git:git://github.com/Dev-Mind/mockwebserver'
developerConnection 'scm:git:git://github.com/Dev-Mind/mockwebserver'
url 'https://github.com/Dev-Mind/mockwebserver'
}
licenses {
license {
name 'The Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'javamind'
name 'Guillaume EHRET'
email 'guillaume@dev-mind.fr'
}
}
}
}
}
}
J’ai suivi la documentation pour tout d’abord générer une clé via
$ gpg2 --key-gen
Lorsque vous générez votre clé vous devez spécifier une passphrase
. Personnellement j’ai du saisir des passphrase
sans espace pour ne pas avoir de problème par la suite quand j’avais à resaisir ces informations.
J’utilise une version récente de Linux qui utilise une version 2.1.15
$ gpg2 --version
Plusieurs choses ont été améliorées dans les versions > 2.1 de gpg. Si vous affichez les clés créées, la taille de ces clés a été augmentée et la nouvelle taille n’est pas encore supportée par le plugin Gradle. Le plugin se base sur les librairies Java org.bouncycastle
et il faut qu’ils migrent vers les dernières versions de cette librairie
$ gpg2 --list-secret-keys
/home/devmind/.gnupg/pubring.kbx
------------------------------
pub rsa2048 2018-01-13 [SC]
6933FACC1931DD8A89CED163582D3134
uid [ultimate] Guillaume EHRET <guillaume@dev-mind.fr>
sub rsa2048 2018-01-13 [E]
Pour retrouver un format de clé court utilisez la commande suivante
$ gpg2 --list-secret-keys --keyid-format short
/home/devmind/.gnupg/pubring.kbx
--------------------------------
pub rsa2048/C6EED57A 2018-01-13 [SC]
uid [ultimate] Guillaume EHRET <guillaume@dev-mind.fr>
sub rsa2048/7DY5B54F 2018-01-13 [E]
Vous pouvez maintenant publier votre clé publique sur un serveur de clé
$ gpg2 --keyserver hkp://pool.sks-keyservers.net --send-keys C6EED57A
Vous pouvez reporter ces informations dans le fichier gradle.properties
global (elles ne doivent pas être envoyées dans votre dépôt de sources public). Ce fichier devra également contenir les identifiants que vous avez utiliser sur Sonatype
signing.keyId=C6EED57A
signing.password=CeciEstMonpassword
signing.secretKeyRingFile=/home/devmind/.gnupg/secring.gpg
ossrhUsername=devmind
ossrhPassword=CeciEstMonpassword
Après avoir fait cette action mon build Gradle ne fonctionnait toujours pas et retournait l’erreur suivante
* What went wrong:
Execution failed for task ':signArchives'.
> Unable to read secret key from file: /home/devmind/.gnupg/secring.gpg (it may not be a PGP secret key ring)
Le stockage des clés à changé. Il ne se fait plus dans un fichier secring.gpg
mais sous forme de sous-répertoires dans le répertoire .gnupg
. Heureusement il est encore possible de générer ce fichier pour assurer la rétrocompatibilité.
$ gpg2 --export-secret-keys > ~/.gnupg/secring.gpg
Un ticket a été ouvert pour modifier le plugin signin de Gradle et une solution a été apportée à partir de Gradle 4.5.
Une autre solution a été mise en place dans la dernière version de Gradle, la version 4.5. Le plugin signing
utilise une implémentation Java pour gérer les signatures va GPG. Cette implémentation ne peut pas utiliser gpg-agent
pour gérer les clés privées. Avec Gradle 4.5 vous pouvez maintenant utiliser cet agent en utilisant useGpgCmd()
(GnuPG doit bien évidemment être installé).
signing {
required { gradle.taskGraph.hasTask("uploadArchives") }
useGpgCmd()
sign configurations.archives
}
Vous devez toujours générer votre clé et l’enregistrer sur le serveur de clé. Vous n’avez plus besoin par contre de générer un fichier pour assurer la rétrocompatibilité. Sans autre configuration, le plugin `signing` trouvera `gpg2` dans le path et vous demandera de saisir la passphrase via une boite de dialogue
Pour automatiser le tout vous pouvez ajouter la configuration suivante dans votre build.gradle global
signing.gnupg.executable=gpg signing.gnupg.useLegacyGpg=false signing.gnupg.keyName=C6EED57A signing.gnupg.passphrase=CeciEstMonpassword ossrhUsername=devmind ossrhPassword=CeciEstMonpassword
Pour plus d’informations vous pouvez lire la documentation.
Une fois que les problèmes vus au paragraphe précédent ont été réglés, vous pouvez publier vos artefacts chez Sonatype.
Les versions suffixées par -SNAPSHOT
sont envoyées vers https://oss.sonatype.org/content/repositories/snapshots/
Les versions tagguées (sans -SNAPSHOT
) sont envoyées vers https://oss.sonatype.org/service/local/staging/deploy/maven2/
Par contre les versions tagguées ne sont pas encore disponible de tous à cette étape. Comme nous l’avons vu au début de l’article les librairies publiées passe d’abord par une phase de recette (staging).
Vous devez lancer le Nexus de Sonatype : https://oss.sonatype.org/#stagingRepositories et sélectionner votre librairie dans le bas de la liste
Dans la barre de bouton le bouton Drop
permet de supprimer votre librairie et le bouton Close
de passer à la phase suivante… Je vous l’accorde ce n’est pas très parlant ce nommage de bouton.
Une fois que vous avez confirmé le passage à l’étape suivante, les contrôles de validité du projet sont lancés.
Vous pouvez cliquer sur le bouton Refresh
pour mettre à jour l’état de votre librairie. Si tout s’est bien passé le bouton Release
dans la barre de bouton s’est activé. En cliquant dessus votre librairie sera publiée et dupliquée sur les différents serveurs Sonatype pour être accessible dans un délai maximal de 2h.