Identifying Maven snapshot artifacts by git revision

Engineering

Learn from our challenges and triumphs as our talented engineering team offers insights for discussion and sharing.

Identifying Maven snapshot artifacts by git revision

Engineering

Our build environment at LiveRamp includes quite a few actively developed library projects, both open-source and internal.  Since engineers are often coordinating changes between libraries and downstream projects when adding or refactoring methods, it’s important to be able to quickly release new versions of these libraries.  To accomplish this we make heavy use of Jenkins continuous integration builds and Maven snapshot versions to quickly push out new artifacts.

Of course, when developing and testing a project which uses one of these shared libraries, it’s important to be able to tell which snapshot version of an upstream library your project has compiled and tested against. When we ported our build systems from Ant + Ivy to Maven earlier this year, we found no good way to log which specific version of a snapshot dependency was resolved.  While the maven-dependency-plugin lists the resolved version of each library, it does not identify the specific build:

[INFO] --- maven-dependency-plugin:2.8:list (default) @ dustin ---
[INFO]
[INFO]    com.liveramp.hank:hank-core:jar:1.0-SNAPSHOT:compile
[INFO]    com.liveramp.megadesk:megadesk-recipes:jar:1.0-SNAPSHOT:compile
[INFO]    com.liveramp:jack:jar:1.0-SNAPSHOT:compile

which makes it impossible to know with confidence which artifact your project is using.

Since we use git version control for all of our projects, we decided the most useful information we could include in the deployed artifact was the git revision of the upstream library.    Getting this information into the manifest of the deployed jar was pretty easy — we tag the MANIFEST.MF of snapshot jars with the latest revision using a combination of buildversion-plugin and maven-jar-plugin:

    <plugin>
       <groupId>com.code54.mojo</groupId>
       <artifactId>buildversion-plugin</artifactId>
       <version>1.0.1</version>
       <executions>
         <execution>
           <goals>
             <goal>set-properties</goal>
           </goals>
         </execution>
       </executions>
     </plugin>

     <!-- Tag the MAINFEST.MF in the jar with the latest git commit-->
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-jar-plugin</artifactId>
       <version>2.4</version>
       <configuration>
         <archive>
           <manifest>
             <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
           </manifest>
           <manifestEntries>
             <!--suppress MavenModelInspection -->
             <Implementation-Build>${build-commit}</Implementation-Build>
           </manifestEntries>
         </archive>
       </configuration>
     </plugin>

Logging this value when resolving dependencies was a bit trickier, and in the end required writing a small maven plugin, which you can find here.  The plugin binds to the compile phase, after the project has resolved dependencies:

@Mojo(name = "print-snapshot-impl",
defaultPhase = LifecyclePhase.COMPILE,
requiresDependencyResolution = ResolutionScope.COMPILE)

fetches all dependencies from the enclosed MavenProject:

@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project; 

…

Set<Artifact> artifacts = project.getArtifacts();

and then fetches from each dependency the build title and revision, if present in the manifest:

      for (Artifact artifact : artifacts) {

        DefaultArtifactVersion version = new DefaultArtifactVersion(artifact.getBaseVersion());

        if (version.getQualifier() != null &&
            artifact.getFile() != null &&
            version.getQualifier().equals("SNAPSHOT")) {

          JarFile jar = new JarFile(artifact.getFile());
          ZipEntry entry = jar.getEntry("META-INF/MANIFEST.MF");
          Manifest manifest = new Manifest(jar.getInputStream(entry));
          Attributes attributes = manifest.getMainAttributes();

          String title = attributes.getValue("Implementation-Title");
          String build = attributes.getValue("Implementation-Build");

          if (title != null && build != null) {
            orderedArtifacts.put(
                artifact.getGroupId() + "." + artifact.getArtifactId() + ":" + version.toString(),
                build
            );

          }
        }
      }

And last prints the versions for each snapshot artifact:

      for (Map.Entry<String, String> entry : orderedArtifacts.entrySet()) {
        getLog().info(entry.getKey() + ":t" + entry.getValue());
      }

Enabling this plugin gives us a nice block of logs which tell us which specific upstream revision we’ve built against:

[INFO] --- liveramp-build-maven-plugin:1.0-SNAPSHOT:print-snapshot-impl (print-snapshot-impl) @ dustin ---
[INFO]
[INFO] Implementation-Build of snapshot dependencies:
[INFO]
[INFO] com.liveramp.cascading_ext:1.6-SNAPSHOT:37ee697eccb55920b94acf5b628111d9be0661be
[INFO] com.liveramp.commons:1.0-SNAPSHOT:fb25deb6a0da77c986bbdbe4ce65ae32ed69896e
[INFO] com.liveramp.hank.hank-client:1.0-SNAPSHOT:83094a18f3ed1392f9bbd389a21c34477cbc3745
[INFO] com.liveramp.hank.hank-core:1.0-SNAPSHOT:83094a18f3ed1392f9bbd389a21c34477cbc3745
[INFO] com.liveramp.hank.hank-server:1.0-SNAPSHOT:83094a18f3ed1392f9bbd389a21c34477cbc3745

 

Certainly not a revolutionary plugin, but having these revisions available in the build logs has saved us a number of debugging headaches.