Eclipse plugin tweaking SourceFolder's output creates duplicates

In our project, we want a different output folder for each source set of the .classpath. To achieve this, I’m using whenMerged, than for each SourceFolder entry, I update its output directory. It works great for the first gradle eclipse command. But then, if we do a another gradle eclipse, existing SourceFolder are not merged properly with the ones Gradle adds. This results in duplicated entries for each source folder. Mainly, I have a src/main/java and src/test/java folders. What I do to update source folders output is:

eclipse.classpath.file.whenMerged { classpath ->
  def sourceFolders = classpath.entries.findAll { it instanceof SourceFolder }

  sourceFolders.each { sourceFolder ->
    sourceFolder.output = "obj/${sourceFolder.path.tokenize('/')[1]}"
  }
}

Project is attached to reproduce: bug-duplicate-eclipse-classpath.zip (1 KB)

Actual console output:

$ gradle -v                                                            
                                                                       
------------------------------------------------------------           
Gradle 2.10                                                            
------------------------------------------------------------           
                                                                       
Build time:   2015-12-21 21:15:04 UTC                                  
Build number: none                                                     
Revision:     276bdcded730f53aa8c11b479986aafa58e124a6                 
                                                                       
Groovy:       2.4.4                                                    
Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM:          1.8.0_25 (Oracle Corporation 25.25-b02)                  
OS:           Windows 7 6.1 amd64                                      

$ tree
.
├── build.gradle
└── src
    ├── main
    │   └── java
    └── test
        └── java

5 directories, 1 file

$ gradle eclipse
Configuration on demand is an incubating feature.
:eclipseClasspath
:eclipseJdt
:eclipseProject
:eclipse

BUILD SUCCESSFUL

Total time: 2.924 secs

$ cat .classpath
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
        <classpathentry kind="output" path="bin"/>
        <classpathentry output="obj/main" kind="src" path="src/main/java"/>
        <classpathentry output="obj/test" kind="src" path="src/test/java"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
</classpath>

$ gradle eclipse
Configuration on demand is an incubating feature.
:eclipseClasspath
:eclipseJdt
:eclipseProject
:eclipse

BUILD SUCCESSFUL

Total time: 2.529 secs

$ cat .classpath
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
        <classpathentry kind="output" path="bin"/>
        <classpathentry output="obj/main" kind="src" path="src/main/java"/>
        <classpathentry output="obj/test" kind="src" path="src/test/java"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
        <classpathentry output="obj/main" kind="src" path="src/main/java"/>
        <classpathentry output="obj/test" kind="src" path="src/test/java"/>
</classpath>

Expected is that the second second writes 2 source folders not 4. Current workaround I have is to actually store duplicate SourceFolder and then remove then from the classpath entries list:

eclipse.classpath.file.whenMerged { classpath ->
  List<SourceFolder> sourceFolders = classpath.entries.findAll { it instanceof SourceFolder }
  Set<String> seenSourceFolderPaths = []
  List<SourceFolder> duplicatedSourceFolders = []

  sourceFolders.each { sourceFolder ->
    if (sourceFolder.path in seenSourceFolderPaths) {
      duplicatedSourceFolders << sourceFolder
      return
    }

    seenSourceFolderPaths << sourceFolder.path
    sourceFolder.output = "obj/${sourceFolder.path.tokenize('/')[1]}"
  }

  duplicatedSourceFolders.each { duplicatedSourceFolder ->
    classpath.entries.remove(duplicatedSourceFolder)
  }
}

This is unfortunately a current limitation when you use the whenMerged hook. The entries you get are the combination of the Gradle model (which contains the “plain” source folders without modifications) and the existing .classpath file (which contains your modified source folders). Since they are not equal, both entries are kept. There is certainly room for improvement here. For instance we could mark entries created by Gradle with a special attribute and then filter them out.

Your workaround looks fine to me. Another possibility would be to do `eclipse.dependsOn(cleanEclipse)’ if you don’t need manual additions.

Had the eclipse.dependsOn(cleanEclipse) workaround first but I found this too intense. Current workaround is hidden in a company’s internal base Gradle plugin, so I find it better than forcing a cleanEclipse.

Can we create a proper bug/improvement entry in the system to not lost track of it?

If I was to work on this, where should I start reading about how to develop Gradle? I tried last year but it did prove rather hard from my memories to actually compile everything. Would like to give it another shot.

Regards,
Matt

I have created GRADLE-3393 to track this.

To work on Gradle, just follow the instructions on Github. I guess you tried developing it with Eclipse. We don’t maintain a working Eclipse setup, as everybody on the team works with IntelliJ. So I suggest using that for a straightforward experience.

If you have any other questions about this issue or contributing, feel free to follow up here :slight_smile:

Yeah, had tried with Eclipse but I think I also had problem from the command line but I can’t recall what it was.

Anyway, it will be a good moment to try out IntelliJ :slightly_smiling: