I made a script in Groovy language to look for newer versions of all declared dependencies in a Maven project. The script supports multi-module projects.
It searches the current folder for pom.xml files and checks the central.sonatype.com website for a newer versions. Only dependencies that have an explicit version defined in pom will be checked. Versions defined by properties will be resolved if the properties were declared in one of the pom files. (If a property is defined more than once, the result is undefined.)
Example of use:
I recommend installing Java 17 (or newer) and Groovy 4 with sdkman!
Put these two files in a folder that is included in PATH.
check-versions.sh
:
#!/usr/bin/env bash
BASEDIR=$(unset CDPATH; cd `dirname $0`; pwd)
JAVA_VER=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{sub("^$", "0", $2); print $1$2}')
if [ "$JAVA_VER" -lt 170 ]; then
echo "Java 17 necessario"
exit 1
fi
#$PROXY="-DproxySet=true -DproxyHost=10.0.0.24 -DproxyPort=8080"
groovy -Dgroovy.grape.report.downloads=true $PROXY $BASEDIR/check-versions.groovy 2>/dev/null
check-versions.groovy
:
import groovy.json.JsonSlurper
import groovy.xml.XmlParser
@Grab('org.jodd:jodd-lagarto:6.0.6')
import jodd.jerry.Jerry
@Grab('org.fusesource.jansi:jansi:2.4.1')
import org.fusesource.jansi.AnsiConsole
@Grab('org.slf4j:slf4j-jdk-platform-logging:2.0.13')
import org.slf4j.Logger
import static org.fusesource.jansi.Ansi.Color.GREEN
import static org.fusesource.jansi.Ansi.Color.RED
import static org.fusesource.jansi.Ansi.ansi
AnsiConsole.systemInstall()
record Dependency(String groupId, String artifactId, String version) {}
static def findAllDependencies() {
//println("-- findAllDependencies")
def allDeps = []
def mvnProperties = [:]
new File(".").traverse(type: groovy.io.FileType.FILES, nameFilter: ~/pom.xml/) {
processPom(new XmlParser().parse(it), allDeps, mvnProperties)
}
return allDeps.unique().sort { k1, k2 -> k1.groupId + k1.artifactId <=> k2.groupId + k2.artifactId }
}
static def processPom(def project, def allDeps, def mvnProperties) {
collectProperties(project, mvnProperties)
collectDependencies(project.'**'.dependencies.dependency, allDeps, mvnProperties)
collectDependencies(project.parent, allDeps, mvnProperties)
collectDependencies(project.'**'.plugin, allDeps, mvnProperties)
}
static def collectProperties(def project, def mvnProperties) {
//println("-- collectProperties")
for (def p : project.properties) {
for (def c : p.children()) {
def name = c.name().localPart
def value = c.text()
mvnProperties[name] = value
}
}
}
static def collectDependencies(def dependencies, def allDeps, def mvnProperties) {
//println("-- collectDependencies")
for (def d : dependencies) {
def groupId = d.groupId.text()
if (groupId.isEmpty()) {
// assume default groupId for maven plugins
groupId = "org.apache.maven.plugins"
}
def artifactId = d.artifactId.text()
def version = d.version.text()
if (version.isEmpty())
continue
if (version.startsWith("\$")) {
def matcher = (version =~ /\$\{(.+?)\}/)
if (matcher.find()) {
def property = matcher.group(1)
version = mvnProperties[property]
if (version == null) {
continue
}
}
}
def x = new Dependency(groupId, artifactId, version)
allDeps.add(x)
}
}
/**
* This method is highly dependable on the html format returned by central.sonatype.com
*
* Return this map:
* [name:logstash-logback-encoder, namespace:net.logstash.logback, version:7.4, versions:[7.4, 7.3, 7.2, 7.1.1, 7.1, 7.0.1, 7.0, 6.6, 6.5, 6.4, 6.3, 6.2, 6.1, 6.0, 5.3, 5.2, 5.1, 5.0, 4.11, 4.10], tags:[], usedIn:1600, tab:versions]
*/
def findLatestVersion(String groupId, String artifactId) {
//println("-- findLatestVersion ${groupId}:${artifactId}")
def url = "https://central.sonatype.com/artifact/${groupId}/${artifactId}/versions"
def body = url.toURL().getText()
def doc = Jerry.of(body)
def scripts = doc.find('html.nx-html.nx-html--page-scrolling body.nx-body script')
def text = ""
scripts.each {
def element ->
if (element.text().contains('self.__next_f.push')) {
text = element.text()
}
}
int i = text.indexOf(':')
int j = text.lastIndexOf('"')
text = text.substring(i + 1, j - 2)
text = text.replaceAll('\\\\"', '"')
text = text.replaceAll('\\\\"', '"')
def json = new JsonSlurper().parseText(text)
try {
def result = json[0][3].children[0][3]
return result
}
catch (Exception ignored) {
return null
}
}
//skipVersions = [:]
skipVersions = [
"org.springframework.boot:spring-boot-starter-parent" : "3.2",
"org.springframework.boot:spring-boot-starter-data-jpa": "3.2",
"org.springframework.boot:spring-boot-starter-web" : "3.2",
"org.springframework.boot:spring-boot-dependencies" : "3.2",
"org.springframework.cloud:spring-cloud-dependencies" : "2023",
]
def getLatestVersion(String groupId, String artifactId) {
//println("-- getLatestVersion")
def versions = findLatestVersion(groupId, artifactId)
def skip = skipVersions["${groupId}:${artifactId}"]
if (skip == null) {
return versions?.version
}
for (def v in versions.versions) {
if (v.startsWith(skip)) {
continue
}
return v
}
}
def allDeps = findAllDependencies()
for (def d : allDeps) {
def latest = getLatestVersion(d.groupId, d.artifactId)
if (latest == null) {
continue
}
print("${d.groupId}:${d.artifactId}:${d.version} ")
if (d.version.equals(latest)) {
println("${ansi().fg(GREEN)}[OK]${ansi().reset()}")
} else {
println("${ansi().fg(RED)}[${latest}]${ansi().reset()}")
}
}
By changing the skipVersions
variable, it is possible to ignore a version of some dependencies.
For example: I'm stuck with Spring Boot 3.1, so the config below will ignore version 3.2, but will still notify for newer 3.1.x version.
skipVersions = [
"org.springframework.boot:spring-boot-starter-parent" : "3.2",
"org.springframework.boot:spring-boot-starter-data-jpa": "3.2",
"org.springframework.boot:spring-boot-starter-web" : "3.2",
"org.springframework.boot:spring-boot-dependencies" : "3.2",
"org.springframework.cloud:spring-cloud-dependencies" : "2023",
]
If there is no dependency to skip, just use an empty map:
skipVersions = [:]
Top comments (4)
I recommend to use the versions-maven-plugin like this:
In particular related to using spring boot including the bom file you can configure the versions-maven-plugin to limit the output on the bom updates instead of the deps updates itself...
mojohaus.org/versions/versions-mav...
These have slightly different behavior.
The plugin will complain about all spring-boot dependencies, even if only
spring-boot-starter-parent
have a explicit version declared:My script will only report on version differences if a dependency has a version explicitly declared in pom files. Only the spring-boot-starter-parent would be reported:
The issue is that you used the plugin based on no configuration... instead of correctly configuring it:
Based on the above configuration you can now call:
That will printout only the updates for the bom's only ... or direct dependencies:
Wow, it's a non-trivial configuration... No wonder I didn't find it in the documentation. Thanks for sharing.