gradle icon indicating copy to clipboard operation
gradle copied to clipboard

Classpath order is unexpected

Open Vampire opened this issue 1 year ago • 4 comments

Current Behavior

I'm reporting this as bug in case it is a code issue, but it could also be that just documentation should make it clear.

Version constraints influence the classpath order, as do the concrete configurations dependencies are added to. To me this comes unexpected. From the past I was under the impressions that the order of classpaths should be controlled by order of declaration in the build script.

If it "works as intended", I'd at least want to ask for clarifying the situation explicitly in the documentation. At least I did not find anything about it.

If you for example have

dependencies {
    runtimeOnly("saxon:saxon:6.5.3")
    implementation("net.sourceforge.nekohtml:nekohtml:1.9.22")
}

and do

System.out.println(SAXParserFactory.newInstance().newSAXParser());

You get Aelfred which is shipped with Saxon 6. But if you do

dependencies {
    constraints {
        implementation("xerces:xercesImpl:2.12.2")
    }
    runtimeOnly("saxon:saxon:6.5.3")
    implementation("net.sourceforge.nekohtml:nekohtml:1.9.22")
}

you "suddenly" get Xerces.

Actually using

val foo by tasks.registering {
    doLast {
        configurations.runtimeClasspath.get().forEach { println(it) }
    }
}

to output the runtime classpath also shows that the order is not in declaration order anyway. Without the constraint you

  • first have neko,
  • then saxon,
  • then the neko dependency xerces,
  • then the xerces dependency xml-apis

With the constraint you

  • first have neko,
  • then xerces,
  • then saxon,
  • then xml-apis

So it seems constraint is enough so that a dependency comes first, and direct implementation dependencies come before direct runtimeOnly dependencies and transitive dependencies for which no constraint exists come after direct ones.

Of course relying on classpath order is bad in most situations and it is usually better to make sure classpaths do not contain classes in multiple jars, or select services explicitly if possible, but sometimes this is just not feasible and manual classpath sorting is also not always possible. In my concrete case the alternative would probably be an artifact transform that removes the service description from the Saxon 6 dependency, but that would imho be quite hacky. :-D

I need the Xerxes parser for parsing, I need Saxon 6 for docbook xsl 1 processing. Saxon has Aelfred parser shipped in the same jar. Manual ordering is not possible as it is in buildSrc dependencies. Setting system property in gradle.properties to explicitly select the parser is also not possible as then the settings script fails as it also parses an XML file and there the built-in parser needs to be used. In the end I just moved the dependencies around so that the resulting order works as I need it in this case.

Expected Behavior

More intuitive classpath ordering, derived from declaration order but independent from actual configuration the dependencies are declared on and version constraints not influencing order.

Alternatively some documentation that clearly documents how classpaths are ordered, or what influences it and so on.

Well, documentation should be added in either case imho.

Gradle version

8.7

Vampire avatar May 23 '24 13:05 Vampire

This issue needs a decision from the team responsible for that area. They have been informed. Response time may vary.


As indicated, the team should decide if the current ordering needs to be better documented or if there is an issue with the current ordering and it should be amended (and then documented).

Note that we have an ordering option available on ResolutionStrategy, but I don't think that is the answer here.

ljacomet avatar May 27 '24 08:05 ljacomet

Note that we have an ordering option available on ResolutionStrategy, but I don't think that is the answer here.

Interesting, didn't know that, thanks. But from what I see I also don't think it is the solution here. But also this might be documented a bit better. The sorting options names are - at least for me - not self-documenting and they don't have any documentation comments.

Vampire avatar May 27 '24 09:05 Vampire

This needs more investigation. I find the ordering even without the constraint to be slightly surprising because I would have thought runtimeOnly came after implementation always.

I'm not sure when we're going to look deeper into this.

big-guy avatar May 28 '24 16:05 big-guy

If the docs are going to be updated, I would mention that it is not just the order. Please see this comment https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1778#issuecomment-2387289484

The presence of constraints on build classpath causes transformations to run again. In the case of described in that comment it means +2-3Gb of consumed disk space, just because of constraints created by dependency locks.

AlexanderBartash avatar Oct 17 '24 14:10 AlexanderBartash