Dependency Management

How jgo handles dependency versions

Maven has a feature whereby a project can override the versions of transitive (inherited) dependencies via a <dependencyManagement> configuration. The problem is: a library may believe it depends on components at particular versions as defined by its <dependencyManagement>, but downstream projects which depend on that library will resolve to different versions. This means the library’s actual dependencies differ from what it was built against.

See this Stack Overflow thread and this gist for full details.

By default, jgo works around this Maven limitation by adding all endpoints to the synthesized POM’s <dependencyManagement> section using import scope. This ensures that the versions of transitive dependencies match those that each endpoint was actually built with.

When multiple endpoints are concatenated via + with conflicting dependency management, earlier endpoints win because they are declared first in the POM.

Disabling managed mode per coordinate

If a specific coordinate should not have its BOM imported into <dependencyManagement>, append ! to the coordinate:

# Resolve scijava-common without importing its dependency management
jgo org.scijava:scijava-common!

# Mix managed and raw in the same endpoint
jgo org.scijava:scijava-common+org.scijava:scripting-jython!

Tip

The ! suffix is rarely needed. It is useful when you want raw Maven transitive resolution for a particular artifact – for example, to debug version conflicts or to intentionally allow Maven’s default “nearest wins” strategy for that coordinate.

See also issue #9 for more discussion.

Version resolution

How RELEASE and LATEST work

When you omit a version (or explicitly write RELEASE), jgo resolves it to the newest release across all configured repositories by comparing version numbers directly. This differs from Maven, which picks the release from whichever repository was most recently updated – a heuristic that can return an older version when artifacts are split across multiple repositories.

For example, if net.imagej:ij version 1.54p is on Maven Central and version 1.48q is on maven.scijava.org, Maven might resolve RELEASE to 1.48q (because maven.scijava.org was updated more recently), while jgo correctly resolves to 1.54p (the highest version number).

LATEST works the same way but includes SNAPSHOT versions. It returns the highest version by Maven version ordering across all repositories, rather than the most recently deployed build.

Checking resolved versions

# Show all versions with markers indicating which is RELEASE and LATEST
jgo info versions org.python:jython-standalone

Resolver options

jgo includes two dependency resolvers:

Pure Python resolver (--resolver python)

Resolves dependencies without a Maven installation. Parses POM files directly, handles transitive dependencies, property interpolation, dependency management (BOMs), exclusions, and scopes.

Maven resolver (--resolver mvn)

Shells out to the system mvn command. Handles all Maven features including plugins and complex profiles.

Auto resolver (--resolver auto, the default)

Tries the pure Python resolver first, falling back to the Maven resolver if needed.

# Force pure Python resolver
jgo --resolver python org.python:jython-standalone

# Force Maven resolver
jgo --resolver mvn org.python:jython-standalone

Known Maven resolver bug: runtime scope promoted to compile

Maven’s dependency resolver has a known bug where runtime-scoped dependencies are reported as compile scope in certain scenarios. This can be observed with jython-slim:

# Python resolver — correctly shows antlr-runtime as runtime scope
jgo tree org.python:jython-slim:2.7.4

# Maven resolver — incorrectly shows antlr-runtime as compile scope
jgo --resolver mvn tree org.python:jython-slim:2.7.4

The jython-slim-2.7.4.pom explicitly declares:

<dependency>
  <groupId>org.antlr</groupId>
  <artifactId>antlr-runtime</artifactId>
  <version>3.5.3</version>
  <scope>runtime</scope>
</dependency>

Despite this, Maven’s dependency:tree and dependency:list output promotes antlr-runtime to compile scope, whereas the pure Python resolver reports the correct scope.