Deployment with gradle

java

I’ve been working with gradle for some time but I’ve never needed to configure deployment from gradle to maven repository. In this post, I’m going to configure deployment of java based gradle project to the nexus artifact repository. This will include uploading a signed jar itself with javadoc and sources.

Before we can start let’s setup nexus repository. I’m going to use docker for this and you can do it easily by simply running:

docker run --name nexus -it -p 8081:8081 sonatype/nexus3:3.15.1

Now you can open http://localhost:8081 to see if everything works just fine if you can see nexus welcome page you are good to go.

Let’s take a quick look at plugins I’ll be using for deployment configuration:

  • Java - duh we need it to compile our java code and generate javadoc and sources jar.

  • Java-library - optional plugin that basically exposes extra dependency scope - api (more on it later).

  • Maven-publish - which will be the workhorse of our deployment process, it will be used to publish artifact to the maven repository.

  • Signing - plugin which can sign artifact using gpg keys.

To sum up, after adding those plugins configuration should contain:

plugins {
    id 'java'
    id 'java-library'
    id 'maven-publish'
    id 'signing'
}

Before publishing anything you should know how dependencies will be configured. Let’s take a look at java-library plugin. The main thing to know is that java-library plugin exposes additional configuration - api:

The api configuration should be used to declare dependencies which are exported by the library API, whereas the implementation configuration should be used to declare dependencies which are internal to the component.

— java-library sepration
https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation

To understand it better we can declare two dependencies in our project

dependencies {
    api 'org.apache.commons:commons-collections4:4.2'
    implementation 'org.apache.commons:commons-lang3:3.5'
    testImplementation 'org.spockframework:spock-core:1.2-groovy-2.5'
}

And use them in following class:

import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

public class SampleClass {
    public BidiMap<String, String> hello(String key, String value) {
        Map<String, String> map = new HashMap<>();
        map.put(StringUtils.lowerCase(key), StringUtils.lowerCase(value));
        return new DualHashBidiMap<>(map);
    }
}

In here we have BidiMap which is part of public API and is exposed to clients whereas commons-lang is an internal implementation detail - commons-collections is part of your API and commons-lang is an implementation detail (extensive explanation).

Adding dependencies using those gradle configurations will produce following dependencies section in pom.xml file:

 <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>

We’ll get to generating pom file soon.

Commons-collections as a part of public API is compile scope. Commons-lang is an internal implementation detail. More on maven dependencies scopes.

Now let’s quickly configure maven-publisher plugin (all of the entries below are required if you want your project to be accepted in central maven repo):

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact sourcesJar
            artifact javadocJar

            from components.java

            pom {
                name = 'Sample project that can be deployed with gradle to nexus'
                description = 'Detailed description of sample project that can be deployed with gradle to nexus'
                url = 'http://www.example.com/library'
                licenses {
                    license {
                        name = 'MIT'
                        url = 'https://opensource.org/licenses/MIT'
                    }
                }
                developers {
                    developer {
                        name = 'Paweł Chudzik'
                        email = 'pawel.chudzik@somewhere.com'
                    }
                }
                scm {
                    connection = 'scm:git:git://example.com/my-library.git'
                    developerConnection = 'scm:git:ssh://example.com/my-library.git'
                    url = 'http://example.com/my-library/'
                }
            }

            repositories {
                maven {
                    def releasesRepoUrl = "http://localhost:8081/repository/maven-releases/"
                    def snapshotsRepoUrl = "http://localhost:8081/repository/maven-snapshots/"
                    url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
                    credentials {
                        username = System.getProperty("nexusUser")
                        password = System.getProperty("nexusPassword")
                    }
                }
            }
        }
    }
}

You might not want to store security credentials directly in your build.gradle file. To keep it simple you can pass credentials using the command line. When you use travisci as your CI tool passwords will be automatically masked for you. When using Jenkins you can install and active mask passwords plugin.

A comprehensive description of the publishing maven plugin and up to the date of example can be found on plugin page where you’ll find more details and options on what can be configured.

At this point we can generate pom file executing gradle task: generatePomFileForMavenJavaPublication (./gradlew generatePomFileForMavenJavaPublication). gradle will generate pom file in directory build/publications/mavenJava/. In our case it’s pretty simple.

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.pchudzik.blog.example.gradledeployment</groupId>
  <artifactId>sample-project</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>Sample project that can be deployed with gradle to nexus</name>
  <description>Detailed description of sample project that can be deployed with gradle to nexus</description>
  <url>http://www.example.com/library</url>
  <licenses>
    <license>
      <name>MIT</name>
      <url>https://opensource.org/licenses/MIT</url>
    </license>
  </licenses>
  <developers>
    <developer>
      <name>Paweł Chudzik</name>
      <email>pawel.chudzik@somewhere.com</email>
    </developer>
  </developers>
  <scm>
    <connection>scm:git:git://example.com/my-library.git</connection>
    <developerConnection>scm:git:ssh://example.com/my-library.git</developerConnection>
    <url>http://example.com/my-library/</url>
  </scm>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

We are almost ready to publish our project but before we can actually do it we should make sure that javadoc and sources will be part of the deployment so users will be able to download sources and documentation from nexus. It’s must have if you want to publish to maven central repo:

task sourcesJar(type: Jar) {
    from sourceSets.main.allJava
    classifier = 'sources'
}

task javadocJar(type: Jar) {
    from javadoc
    classifier = 'javadoc'
}

artifact sourcesJar and artifact javadocJar in publishing plugin configuration

Now we are ready to publish our project to nexus by simply running:

./gradlew clean publish -DnexusUser=admin -DnexusPassword=admin123

Your corporate artifactory/nexus might accept not signed artifacts but if you want to deploy something to central maven repo you must sign your artifacts with gpg key. Good how to instruction can be found on maven central website.

To sign artifact you should add signing plugin and configure additional task (simply put it after publishing task configuration):

signing {
    useGpgCmd()
    sign publishing.publications.mavenJava
}

With this, you are good to go and ready to publish your changes to your corporate nexus or to public maven repository.

See Also

If you've enjoyed or found this post useful you might also like:

6 Feb 2019 #gradle #mvn