/ Apache Maven Build Tool

Dependency version reports with Maven

I recently was in the situation that I inherited an old project and the first thing I wanted to do was to get the project updated to more recent dependencies to fix security vulnerabies and to start supporting new technologies that require more modern versions of the libraries.

So the thing I wanted to create was something I could run on a CI and that could generate a report I could put on a dashboard where I could see what dependencies were getting old and what I needed to update.

Enter the Maven versions plugin.

With the versions plugin you can pretty easily get reports of what dependencies you have and what versions they have. But one perticular feature is really interesting here, and that is the display-plugin-updates feature of the Versions plugin.

If I run mvn versions:display-dependency-updates versions:display-plugin-updates on a small example project you might get the following output:

$ mvn versions:display-dependency-updates versions:display-plugin-updates
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building dependency-report Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- versions-maven-plugin:2.5:display-dependency-updates (default-cli) @ dependency-report ---
[INFO] The following dependencies in Dependencies have newer versions:
[INFO]   junit:junit ............................................ 3.8.1 -> 4.12
[INFO]   log4j:log4j ......................................... 1.2.12 -> 1.2.17
[INFO]   org.hsqldb:hsqldb ..................................... 2.3.3 -> 2.4.0
[INFO]   org.jsoup:jsoup ...................................... 1.8.3 -> 1.11.2
[INFO] 
[INFO] 
[INFO] --- versions-maven-plugin:2.5:display-plugin-updates (default-cli) @ dependency-report ---
[INFO] 
[INFO] All plugins with a version specified are using the latest versions.
[INFO] 
[WARNING] The following plugins do not have their version specified:
[WARNING]   maven-clean-plugin .......................... (from super-pom) 2.2
[WARNING]   maven-compiler-plugin ..................... (from super-pom) 2.0.2
[WARNING]   maven-deploy-plugin ......................... (from super-pom) 2.4
[WARNING]   maven-install-plugin ........................ (from super-pom) 2.2
[WARNING]   maven-resources-plugin ...................... (from super-pom) 2.2
[WARNING]   maven-site-plugin .................... (from super-pom) 2.0-beta-4
[WARNING]   maven-surefire-plugin ..................... (from super-pom) 2.4.2
[WARNING]   maven-war-plugin ............................ (from super-pom) 2.0
[INFO] 
[WARNING] Project does not define minimum Maven version, default is: 2.0
[INFO] Plugins require minimum Maven version of: 3.0
[INFO] Note: the super-pom from Maven 3.3.9 defines some of the plugin
[INFO]       versions and may be influencing the plugins required minimum Maven
[INFO]       version.
[INFO] 
[ERROR] Project does not define required minimum version of Maven.
[ERROR] Update the pom.xml to contain maven-enforcer-plugin to
[ERROR] force the maven version which is needed to build this project.
[ERROR] See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html
[ERROR] Using the minimum version of Maven: 3.0
[INFO] 
[INFO] Require Maven 2.0.1 to use the following plugin updates:
[INFO]   maven-war-plugin .............................................. 2.0.2
[INFO] 
[INFO] Require Maven 2.0.2 to use the following plugin updates:
[INFO]   maven-site-plugin ........................................ 2.0-beta-7
[INFO] 
[INFO] Require Maven 2.0.6 to use the following plugin updates:
[INFO]   maven-clean-plugin .............................................. 2.5
[INFO]   maven-deploy-plugin ........................................... 2.8.1
[INFO]   maven-install-plugin .......................................... 2.5.1
[INFO]   maven-resources-plugin .......................................... 2.6
[INFO]   maven-site-plugin ............................................. 2.0.1
[INFO]   maven-surefire-plugin ......................................... 2.4.3
[INFO]   maven-war-plugin ................................................ 2.4
[INFO] 
[INFO] Require Maven 2.0.9 to use the following plugin updates:
[INFO]   maven-compiler-plugin ........................................... 3.1
[INFO]   maven-surefire-plugin .......................................... 2.17
[INFO] 
[INFO] Require Maven 2.1.0 to use the following plugin updates:
[INFO]   maven-site-plugin ............................................. 2.1.1
[INFO] 
[INFO] Require Maven 2.2.0 to use the following plugin updates:
[INFO]   maven-site-plugin ............................................... 3.0
[INFO] 
[INFO] Require Maven 2.2.1 to use the following plugin updates:
[INFO]   maven-clean-plugin ............................................ 2.6.1
[INFO]   maven-compiler-plugin ........................................... 3.3
[INFO]   maven-deploy-plugin ........................................... 2.8.2
[INFO]   maven-install-plugin .......................................... 2.5.2
[INFO]   maven-resources-plugin .......................................... 2.7
[INFO]   maven-site-plugin ............................................... 3.4
[INFO]   maven-surefire-plugin .......................................... 2.20
[INFO]   maven-war-plugin ................................................ 2.6
[INFO] 
[INFO] Require Maven 3.0 to use the following plugin updates:
[INFO]   maven-clean-plugin ............................................ 3.0.0
[INFO]   maven-compiler-plugin ......................................... 3.7.0
[INFO]   maven-resources-plugin ........................................ 3.0.2
[INFO]   maven-site-plugin ............................................... 3.7
[INFO]   maven-surefire-plugin ........................................ 2.20.1
[INFO]   maven-war-plugin .............................................. 3.2.0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.816 s
[INFO] Finished at: 2018-02-19T17:51:02+02:00
[INFO] Final Memory: 20M/473M
[INFO] ------------------------------------------------------------------------

That is great as it displays all the information we need!

But it is not nice to have to go through the build logs every time to see if a dependency has changed in that output. What I instead would like to do is generate a HTML report for every build that only displays the dependencies in table form. And unfortunately the Versions plugin does not have that feature so I had to improvize.

Our hero, Groovy enters the stage!

Here is a script that will turn the above logs into a html page you can directly view (Download):

#!/usr/bin/groovy
import groovy.xml.MarkupBuilder

def lines = System.in.newReader().readLines()
        // Only get INFO lines and the module header
        .grep { String line -> line.startsWith('[INFO]   ') || line.contains('Building ')}
        // Remove the INFO tag
        .collect { String line -> (line.split(' ') - '[INFO]').join(' ') }

lines = lines
        // Fix lines that has wrapped
        .eachWithIndex { String line, int idx ->
            if(!line.contains('...') && line.contains('->')){
                lines[idx-1] << line
                lines[idx] = ''
            }
        }

        // Only get version upgrades and module header
       .grep { it.contains('->') || it.startsWith('Building')}

        // Replace dots and split into list of arrays
       .collect { it
                    .replaceAll('(\\.){2,}', ':')
                    .replaceAll('->', ':')
                    .replace('Building ','')
                    .split(':')
        }

// Render HTML
new MarkupBuilder().html {
    head {
        style {
            mkp.yield """
                   .datagrid table { border-collapse: collapse; text-align: left; width: 100%; } 
                   .datagrid {font: normal 12px/150% Arial, Helvetica, sans-serif; background: #fff; overflow: hidden; border: 1px solid #36752D; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; }
                   .datagrid table td, 
                   .datagrid table th { padding: 3px 10px; }
                   .datagrid table thead th {background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #36752D), color-stop(1, #275420) );background:-moz-linear-gradient( center top, #36752D 5%, #275420 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#36752D', endColorstr='#275420');background-color:#36752D; color:#FFFFFF; font-size: 15px; font-weight: bold; border-left: 1px solid #36752D; } 
                   .datagrid table thead th:first-child { border: none; }
                   .datagrid table tbody td { color: #275420; border-left: 1px solid #C6FFC2;font-size: 12px;font-weight: normal; }
                   .datagrid table tbody .alt td { background: #DFFFDE; color: #275420; }
                   .datagrid table tbody td:first-child { border-left: none; }
                   .datagrid table tbody tr:last-child td { border-bottom: none; }
                   .datagrid table tfoot td div { border-top: 1px solid #36752D;background: #DFFFDE;} 
                   .datagrid table tfoot td { padding: 0; font-size: 12px } 
                   .datagrid table tfoot td div{ padding: 2px; }
                   .datagrid table tfoot td ul { margin: 0; padding:0; list-style: none; text-align: right; }
                   .datagrid table tfoot  li { display: inline; }
                   .datagrid table tfoot li a { text-decoration: none; display: inline-block;  padding: 2px 8px; margin: 1px;color: #FFFFFF;border: 1px solid #36752D;-webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #36752D), color-stop(1, #275420) );background:-moz-linear-gradient( center top, #36752D 5%, #275420 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#36752D', endColorstr='#275420');background-color:#36752D; }
                   .datagrid table tfoot ul.active, .datagrid table tfoot ul a:hover { text-decoration: none;border-color: #275420; color: #FFFFFF; background: none; background-color:#36752D;}
                   div.dhtmlx_window_active, div.dhx_modal_cover_dv { position: fixed !important; }
             
            """
        }
    }
    body {
        h1 {
            mkp.yield "Dependency Report ${new Date()}"
        }
        h3 {
            mkp.yield 'Dependencies that are not up-2-date:'
        }
        div(class:'datagrid'){
            table {
                lines.each { artifact ->
                    if(artifact.length == 1) {
                        tr(class: 'module') {
                            th {
                                mkp.yield artifact[0]
                            }
                            th {
                                mkp.yield 'Current'
                            }
                            th {
                                mkp.yield 'Latest'
                            }
                        }
                    } else {
                        def currentVersion = artifact[2].tokenize('.')
                        def latestVersion = artifact[3].tokenize('.')
                        def severity = 'auto'
                        if(currentVersion[0] != latestVersion[0]){
                            severity = 'red'
                        } else if (currentVersion[2] != latestVersion[2]){
                            severity = 'yellow'
                        }

                        tr {
                            td(class: 'artifact') {
                                a(href: "https://mvnrepository.com/artifact/${artifact[0].trim()}/${artifact[1].trim()}/${artifact[3].trim()}") {
                                    mkp.yield artifact[0] + ':' + artifact[1]
                                }
                            }
                            td(class: 'current', style: "background-color:$severity") {
                                mkp.yield artifact[2]
                            }
                            td(class:'latest') {
                                mkp.yield artifact[3]
                            }
                        }
                    }
                }
            }
        }
    }
}

To use this script you simply pipe the output of the Maven versions plugin through it and it will turn that log into a nice html page.

$mvn versions:display-dependency-updates versions:display-plugin-updates | groovy maven-reports.groovy > report.html

And here is how it will look:

maven-dependency-report

Now all you need to do is generate that on every build and put up the report on a dashboard and your project never has a old dependency in it!