Eclipse classpath ordering - project dependency always at the bottom of dependencies

(Luca Domenichini) #1

I have a multiproject build, with GradleProject depending on GradleBase.
GradleBase depends on some libraries, e.g. commons-configuration.

Suppose I need to hack a class in commons-configuration: I can copy the source code from that class and paste on project GradleBase. Everything works fine when running from GradleBase.

The problem arises when I use that class from GradleProject: in the eclipse classpath the GradleBase dependency is always at the bottom of the list:

So the original class from commons-collection is loaded and my code in never executed.

Is there a way to put the project dependency on top of the list?

I tried something like this, to reorder the entries in classpath, but the order is always the same:

eclipse {
    classpath {
        file {
            whenMerged { cp ->
                cp.entries.sort(new Comparator() {
                    public int compare(Object e1, Object e2) {
                        int cmp = 0;
                        if (e1 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency
                            && e2 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency)
                            cmp = 0;
                        else if (e1 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency == false
                            && e2 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency)
                            cmp = -1;
                        else if (e1 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency
                            && e2 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency == false)
                            cmp = 1;
                        return cmp;

Same happens if I use beforeMerged {}
Is there another way to do that?


(uklance) #2

Perhaps you could approach this another way and instead tweak the jar to remove the offending class file. Then you wouldn’t need to rely on classpath ordering.

configurations {
   commonsConfig { transitive = false }
dependencies {
    commonsConfig "commons-config:commons-config:1.10"
    compile files("$buildDir/tweaked-commons-config.jar")
task tweakCommonsConfig(type:Jar) {
   from zipTree(configurations.commonsConfig.singleFile).matching {
      exclude 'path/to/some.class'
   destinationDir buildDir
   archiveName 'tweaked-commons-config.jar'
compileJava.dependsOn tweakCommonsConfig

Same package from two jar (ambiguity)
(Luca Domenichini) #3

I understand your approach, but I would prefer to be able to shadow a class without going into this kind of pre-compile-packaging; as I understand, I would need to list every file I want to shadow in build.gradle. I think this is not very straight forward as simple classpath ordering.

I think that since classpath ordering is an issue in Java (using gradle or not), Gradle should provide a way to manage it. I understand that runtime class shadowing and classpath ordering is not good programming technique and may lead to obscure errors, but that is Java (at least Java 8)!

(uklance) #4

I would need to list every file

Not necessarily… You could write a filter that includes every class file that doesn’t have a corresponding java file in your project

Considering commons config is open source, another approach would be to download the sources from maven central and compile a new jar with your overridden classes

Or better yet, if it’s a bug you’re fixing… Contribute the fix to commons config

(Luca Domenichini) #5

Wow! Gradle is indeed powerful! Would you figure me out some idea about how to do that?

Still, I think the build script becomes quickly complicated adding such things… classpath ordering would still be better… but, wait… is it possible to do classpath ordering in gradle?

(uklance) #6

Would you figure me out some idea about how to do that?

task tweakCommonsConfig(type:Jar) { 
   def classList = [] 
   fileTree('src/main/java').matching {
      include 'org/apache/commons/**/*.java'
   }.visit { FileVisitDetails fvd ->
      classList << fvd.relativePath.pathString.replace('.java', '.class')
   from zipTree(configurations.commonsConfig.singleFile).matching { 
      exclude classList
   destinationDir buildDir 
   archiveName 'tweaked-commons-config.jar' 

is it possible to do classpath ordering in gradle?

Probably… I just think it’s hacky and likely won’t be applied properly in any project consuming yours

(Luca Domenichini) #7

I see your point. You are right. But my project won’t be consumed by any other project, since it’s the final frontend…

(Stefan Oehme) #8

There is no way to reorder them, because project and external dependencies are handled as two distinct buckets in the Tooling API. But I agree projects should come first so we can support this use case. @donat do you think this is something we can fit into 2.0.1?

(Donát Csikós) #9

I think so, moving the projects before the binary dependencies in the classpath container should not be a big deal.

(Luca Domenichini) #10

Great! Very fast reply!

(Donát Csikós) #11

I did a quick check and I have some bad news. It seem that the change breaks the fix we did for Fortunately only projects with older Gradle versions (2.5-) are affected.

Besides, there’s another problem. Even if I change the classpath ordering, it is not reflected in the package explorer view. In other words, projects under the “Project and External Dependencies” node are placed after the libraries even if they come before in the project’s classpath. This will likely confuse other users.

(uklance) #12

But my project won’t be consumed by any other project, since it’s the final frontend

Is this a web application producing a war? I think the servlet specification is a bit vague on classpath ordering so another reason NOT to do this

(Luca Domenichini) #13

This would be very confusing! Unfortunately, I have not enough knowledge to help you… I still think that classpath ordering, since it is possible in java, should be possible in gradle/buildship too… see what you can do…

It’s a java se application (I wrote frontend without being too precise…).
I mean it is not a library but the final app deployed to the customer, so no one else will use that.

(uklance) #14

I mean it is not a library but the final app deployed to the customer

You’ll need to ensure that the app startup script has the exact same classpath ordering as your eclipse classpath. Whilst doable I still feel this is hacky considering there’s the option to remove the duplicates from the classpath

(Luca Domenichini) #15

Ok, but while removing the duplicates from the classpath, you are imposing ordering too! Infact, excluding some class from a jar because I have a copy of that in my project, means that my project “comes first” the library… offcourse, you are doing that at compile time instead of runtime, which is generally better.

But, consider that in my use case I package all of my classes in a single jar that has the entry point of the application (main method); I create a Class-Path manifest entry (at build time) on the main jar, linking to all needed libraries, so the launch script is simply “java -cp myjar.jar”. This way, I don’t have to care about ordering both at compile time and at runtime. The real problem is within Eclipse during debug…

(uklance) #16

Would this be true for composite builds? ie would the classpath ordering change when a project is included in a composite? Because that sounds like a bad idea to me

(uklance) #17

removing the duplicates from the classpath, you are imposing ordering too

By removing the duplicates it means consumers don’t need to concern themselves with ordering. One consumer is eclipse, the other is the app startup script. Unit tests and integration tests could also be considered as consumers.

With your approach you’ll need to ensure every consumer has the right path ordering, this will likely require duplication of sorting logic across eclipse, junit and your startup script.

With my solution the problem disappears