How to deploy artifact to the Maven Central Repository

It was quite a long time ago when I wrote the last blog post. Sometimes it just takes time till inspiration finds you… but do not rush forward!
Last year I promised: I will report about growing tomatoes and I keep my word.

Motivation

You have built something niche and you want to give back something to the community, right? Even if the idea (and the implementation) is revolutionary it will not be used by anyone if it is not accessible somehow. To be concrete: if we talk about a jar then this artifact must be available in the Maven Central Repository – or at least in a public repository somewhere. Since Maven Central Repository is the default repo the best is to have our world savior there.

I went through some tutorials but some of them were outdated while others worked only on Linux/Mac. Since I am more a Windows guy I had to walk sometimes in the dark. I do not say that it was difficult but this is something that no one does too frequently thus in a few years when I buy my next notebook I will just scratch my head: what have I done to make it work?

Prerequisites

I assume you have a basic understanding of

  • Windows
  • Git
  • Java
  • Maven

Steps

  1. Create your account on Github
  2. Upload your Maven project and take java-offline-geoip as an example
  3. Study the pom.xml, the important sections are
    • <version>${semver}</version>
    • <distributionManagement>
          <snapshotRepository>
              <id>ossrh</id>
              <url>https://oss.sonatype.org/content/repositories/snapshots</url>
          </snapshotRepository>
          <repository>
              <id>ossrh</id>
              <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
          </repository>
      </distributionManagement>
    • <scm>
          <url>https://github.com/tornaia/java-offline-geoip</url>
          <connection>scm:git:https://github.com/tornaia/java-offline-geoip.git</connection>
          <developerConnection>scm:git:git@github.com:tornaia/java-offline-geoip.git</developerConnection>
      </scm>
    • <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-source-plugin</artifactId>
          <version>${maven-source-plugin.version}</version>
          <executions>
              <execution>
                  <id>attach-sources</id>
                  <goals>
                      <goal>jar</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>
    • <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>${maven-javadoc-plugin.version}</version>
          <configuration>
              <additionalOptions>
                  <additionalOption>-html5</additionalOption>
              </additionalOptions>
          </configuration>
          <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>${maven-gpg-plugin.version}</version>
          <executions>
              <execution>
                  <id>sign-artifacts</id>
                  <phase>verify</phase>
                  <goals>
                      <goal>sign</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>
    • <plugin>
          <groupId>org.sonatype.plugins</groupId>
          <artifactId>nexus-staging-maven-plugin</artifactId>
          <version>${nexus-staging-maven-plugin.version}</version>
          <extensions>true</extensions>
          <configuration>
              <serverId>ossrh</serverId>
              <nexusUrl>https://oss.sonatype.org</nexusUrl>
              <autoReleaseAfterClose>true</autoReleaseAfterClose>
          </configuration>
      </plugin>
    • <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-scm-plugin</artifactId>
          <version>${maven-scm-plugin.version}</version>
          <configuration>
              <tag>${project.artifactId}-${project.version}</tag>
          </configuration>
      </plugin>
  4. Create a Sonatype account
  5. Open ticket to create your new project and take mine as an example
  6. Install GnuPG
  7. Create a new key pair with Kleopatra: add your name and email too
    • Remember for your passphrase
    • Make a backup of your key pair
    • Generate a revocation certificate – later you need to edit it manually to avoid accidental use
    • Keep these files in a very safe place
  8. Export your certificate to an OpenPGP Keyserver
    1. Configure Kleopatra by right clicking on its system tray icon
    2. Set OpenPGP Keyserver to hkp://pool.sks-keys under Directory Services
    3. Apply
    4. Open Kleopatra by a single click on the system tray icon
    5. Publish it by right clicking on the certificate and selecting Publish on server…
  9. Create/Edit ~/.m2/settings.xml
    • <settings>
        <servers>
          <server>
            <id>ossrh</id>
            <username>YOUR_SONARTYPE_ACCOUNT_NAME</username>
            <password>YOUR_SONARTYPE_ACCOUNT_PASSWORD</password>
          </server>
        </servers>
        <profiles>
          <profile>
            <id>ossrh</id>
            <activation>
              <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
              <gpg.executable>gpg</gpg.executable>
            </properties>
          </profile>
        </profiles>
      </settings>
  10. Deploy your artifact: mvn clean deploy scm:tag -Dsemver=0.0.1
    • It will ask for your certificate’s passphrase
    • After you promoted your first release you need to add a comment to your “new project” ticket then central sync will be activated
    • After you successfully release, your component will be published to Maven Central Repository, typically within 10 minutes, though updates to search.maven.org can take up to two hours
    • If the version ends with -SNAPSHOT then it will go to the snapshots repository – but I know you know Maven very well! 😉

Misc

I was able to deploy snapshots but somehow I had no rights to deploy releases and encountered the following error:
you have no permissions to stage against profile with ID "62d9bff4b07028"? Get to Nexus admin...
I was not the only one with this so I guess it is a common issue but after opening a Jira ticket it just worked.

Large Scale Refactor III. – Maven

Niceties in my life: participating in an another superb wedding party, settling down in an almost new apartment in the heart of the preferred area – Grüezi von Opfikon! – and assembling furnitures there. I dedicate this post to my favorite piece of equipment: Nespresso Inissia and its invigorating effects.
The cover image was taken of the Kapellbrücke during our recent small trip to Luzerne.

Review

Now I have a small bunch of tests so I have a continuously growing and living specification. The following scenarios are supported:

  • moving dependency to a new/existing module (sibling, parent, child)
  • moving all java classes depending – even transitively – on the what if the from does not provide the referenced classes by any other dependency
  • resolving properties in maven coordinate (groupId, artifactId, version) tags even if there are inherited properties
  • dependencyManagement is taken into consideration
  • preliminary check: to might contain what but in this case the version must match otherwise the user is asked to resolve the conflict first
  • give insight to the user about the completed operations via Intellij’s Event Log panel

Glossary

what: trivial, the dependency you want to move
from: trivial, the maven module that includes the what
as/to: as is referring to a new, non-existing module while to is marking an existing one
parentTo: the parent of the as/to module

Small lessons learned

Do not use together two dependencies where both contain a class with the same FQCN

com.google.collections:google-collections:1.0 and com.google.guava:guava:18.0 both contain class com.google.common.collect.Iterables with method: void removeIf(Iterable, Predicate). In google-collections it has no modifier so its package-private while the guava provides this as public. This caused a runtime Error sometimes because the classloader found the stricter one:
java.lang.IllegalAccessError: tried to access method com.google.common.collect.Iterables.removeIf(Ljava/lang/Iterable;Lcom/google/common/base/Predicate;)Z

Spin-off: It should be an easy task to implement a validator by reusing the existing code to cover this scenario but at the moment I do not want to lose the focus.

No automated way to publish to JetBrains IntelliJ IDEA Plugin Repository

At the moment there is no automated way to publish a new version of a plugin to the official plugin repo  – according to their support page.

Spin-off: Let’s create a Jenkins plugin for this!

Use Idea’s built in diagram drawer

I used an another tool so far: com.github.ferstl:depgraph-maven-plugin. It was easy to integrate into a build process but it is not as handy as a fully integrated solution in an IDE.

maven-intellij-idea-maven-dependencies-diagram

Planning

Focus on splitting Java classes:
ClassAB depends on DepA and DepB. Let’s move DepB to an another module: split ClassAB into two: ClassA remains in the same place but ClassB must be moved to the new module. Update the dependencies of other modules, fix imports and references in Java classes. The code must compile after the transformation.

Epilogue

Do not think that even the highlighted scenarios are all well-rounded. I track all popping up idea within a Google spreadsheet and it is growing super fast. The more I work on this project the more idea I have and the more I realize how much I do not know about Java and Maven. Most of the time I have nothing but a dim and uncertain assumption.

Large Scale Refactor II. – PoC

After travelling 1.5 hours by public transport from Zürich to Intschi and taking the cable car up to Arnisee the hiking finally started. The top, Sunniggrätli offers not just an amazing view but a small mountain hütte too: I have never had a lunch with such an amazing view. The cover image was taken from the dining table: no filters, no photoshop. At the end of the day we climbed – according to my FitBit Blaze – 296 floors, walked 19.9 Kilometers and burned 5453 calories. We took not just the aching toes home but a lifetime experience too. If you have the opportunity to hike in the Canton of Uri then do not hesitate!

First version

I published what I have so far on GitHub and my mind is really full with TODOs and FIXMEs. At the moment I strongly do not recommend to install it unless you want nothing but play with it.

I called three libraries for help: ShrinkWrap to resolve Maven dependencies, JavaParser and ASM to parse Java source and class files.

Some really basic scenarios are already implemented and covered with automated tests. I had two options: building up an object structure representing the Maven modules then executing the transformation steps on them. The other way is to create real Maven projects: folders and source files on the file system and using them as input and writing the results back to the disk. I voted for the second but implemented a few but real unit tests beside these high level ones.

Jenkins helped me to make the build more environment independent. Some issues immediately popped out: ignoring different line endings (CR, LF) and indentation is now solved.

large-scale-refactor-ui-first-version
I am aware of its ugliness

Next

A more aesthetic UI, much more handled scenarios and figure out how to push LSR to JetBrains IntelliJ IDEA Plugin Repository.

Large Scale Refactor I. – An idea

I am sending out Anmeldeformular für Wohnen, Betreibungsauszug and Kopies auf der Aufenthaltsbewilligung once or sometimes twice each day mostly via e-mail rarely traditional post. Setting up appointments with landlords and actual tenants is my new favorite daily routine. Getting to know the city is just one benefit of searching for a new apartment, however I am now ready to leave behind this joy. Sooner or later it will happen… keep fingers crossed!

The problem

Do you know the single responsibility principle and separation of concerns? Have you ever had a huge and complex module where it – would – took tremendous of time to make the first cut or dependency extraction to simplify it?
Probably after some time you gave up for some reason. Maybe the next super important business feature must be implemented asap… and the code is rotten and more rotten. What if this redesign could be like method renaming? Shift + F6 or Alt + Shift + R!

An example

There is a huge module “core” with many dependencies: logging, persistence, web/servlet, etc…

You want to move all logging related dependencies to a new “core-logging” module. So you select logback-core, logback-classic, slf4j-api, jcl-over-slf4j and with one click they are all moved to a new module as well as all the related classes.

As a next step you will move the hibernate/jpa/db/sql dependencies exactly like this to a new “core-persistence” module.

I know: it is a super easy and trivial example. What about really mean and complex scenarios? Will it work? Is this a good idea at all?

Next

A primitive POC is under development and at first I will create a simple plugin for IntelliJ IDEA around it. It will be publish on GitHub soon!