Generated eclipse classpath can contain duplicate entries

eclipse
not-a-bug

#1

In the case of my project below the generated ‘.classpath’ file for eclipse contains a duplicate entry for a dependency. To generate the ‘.classpath’ file I used gradlew cleanEclipse eclipse. This produces a build path error when you try to import the project into eclipse mars 4.5.
This does not happen in Gradle 2.4 or below and is most likely related to this: https://docs.gradle.org/2.5/release-notes#changes-in-ide-classpath-generation
This bug might also be related: Duplicate eclipse entry in classpath file when main and sub-projects share a dependency AND downloadSources = true

As a workaround I leave my project at Gradle 2.4, but that is obiously not very good.

Here is the Project:

rootproject:

apply plugin: 'java'
apply plugin: 'eclipse'

task wrapper(type: Wrapper) { gradleVersion = '2.11' }

ext.libs = [
  guava: 'com.google.guava:guava:18.0'
]

allprojects {
  repositories { mavenCentral() }
}

dependencies { compile project(':sub') }

subproject:

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'ear'
apply plugin: 'eclipse'

dependencies {
  compile 'com.google.guava:guava:18.0'
}

FileCollection nonProvidedRuntime = configurations.runtime - configurations.providedRuntime

dependencies { earlib nonProvidedRuntime }

Example for generated classpath file with duplicate entry:

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
    <classpathentry kind="output" path="bin"/>
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
    <classpathentry kind="src" path="/sub"/>
    <classpathentry sourcepath="C:/Users/Uffmanna/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/18.0/ad97fe8faaf01a3d3faacecd58e8fa6e78a973ca/guava-18.0-sources.jar" kind="lib" path="C:/Users/Uffmanna/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/18.0/cce0823396aa693798f8882e64213b1772032b09/guava-18.0.jar"/>
    <classpathentry kind="lib" path="C:/Users/Uffmanna/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/18.0/cce0823396aa693798f8882e64213b1772032b09/guava-18.0.jar"/>
</classpath>

Here is the complete sample project
gradle-ear-eclipse-bug.zip (54.7 KB)


#2

This is still an issue in Gradle 2.14


#3

Here is a workaround that fixes this issue by removing all duplicate entries:

eclipse {
  classpath {
    file {
      whenMerged { cp ->
        logger.lifecycle "Removing duplicate classpath entries from eclipse for project '${project.name}'"

        Map entryByPath = cp.entries.groupBy { entry -> entry.path }
        entryByPath.each { key, values ->
          if (values.size() > 1) {
            def entry = values.first()
            if (entry.kind == 'src') {
              entry.includes = []
              entry.excludes = []
            }
            int index = cp.entries.indexOf entry
            logger.lifecycle "Removing ${values.collect { it.path }}"
            cp.entries.removeAll values
            logger.lifecycle "Adding ${entry.path}"
            cp.entries.add index, entry
          }
        }
      }
    }
  }
}

(Stefan Oehme) #4

This is not what you want - it eagerly resolves the dependencies at that point and treats them as a simple local file collection instead of a collection of external dependencies.

As far as I can see you want all runtime dependencies to be in the earlib. You could just do configurations.earLib.extendsFrom(configurations.runtime). The providedRuntime dependencies are removed automatically.


#5

While your solution avoids the eclipse error the generated ear is no longer correct. The providedRuntime dependencies are not automatically removed from the earlib configuration.

By extending the main build file to contain a provided dependency you can see the difference in the generated ear.

extended build.gradle:

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'ear'
apply plugin: 'eclipse'

configurations.earlib.extendsFrom(configurations.runtime)

repositories {
  mavenCentral()
}

dependencies {
  compile 'com.google.guava:guava:19.0'
  providedCompile 'log4j:log4j:1.2.17'
}

//FileCollection nonProvidedRuntime = configurations.runtime - configurations.providedRuntime
//
//dependencies { earlib nonProvidedRuntime }

When running gradlew ear the generated ear contains both guava and log4j in it’s libs directory.
If you remove the extendsFrom and comment in my code, the ear file only contains the guava jar as a lib, wich is what I want, but that of course results in the eclipse problem.


(Stefan Oehme) #6

Sorry, I see what you mean now.

You’ll have to do this one level lower, adding the files you want as inputs to the ear task and telling Eclipse about the extra configuration you want to include in the assembly.

ear.lib {
  from configurations.runtime - configurations.providedRuntime
}

eclipse.wtp.component {
  libConfigurations << configurations.runtime
}