Transitive test jars are being included in dependent war project when deployed or exported in eclipse


(Adam Charlton) #1

Hi There,

I’ve run into an small problem with a multi project build where transitive testCompile dependencies make it into the deployed artifact for a war project has a compile dependency on another project which has testCompile dependencies.

When deployed to a TomEE server or exported as a war file the testCompile dependencies get loaded into the war file as well.

I’ve been trying to figure out how to configure the eclipse plugin so that these transitive test dependencies don’t get deployed along with the needed compile dependencies.

I’ve created a sample project which replicates the issue here - https://docs.google.com/file/d/0B2kcDxmenZW5dGt4ZGUxWlQ5Q3M/edit?usp=sharing

First time I’ve had to share a file on a forum so if you can’t access it let me know where you would like it.

I imported the projects into eclipse as Gradle projects, performed a clean build, exported the war project and then checked the WEB-INF/lib of the exported war to verify that the testCompile dependencies were there.

I encountered this issue as part of a larger project where the deployment to TomEE caused an error because the test tomcat jars were being included in the application classloader which was interfering with the normal startup of the app.

If there’s anything else I can provide or any questions I can answer please let me know.

Cheers,

Adam


(Adam Charlton) #2

An update on this.

I’ve found two ways to resolve this issue however I’m not sure which way is more correct.

1 - Use withXml from the eclipse classpath DSL to modify the .classpath file

eclipse {
  classpath {
    withXml { xml ->
     def node = xml.asNode()
     (configurations.testCompile - configurations.compile).each { dependency ->
      node.remove( node.find { it.@path == "${dependency.path.replace('\', '/')}" } )
      node.appendNode( 'classpathentry', [ kind: 'lib', path: "$dependency.path"])
     }
    }
   }
  }
 }

2 - Modify the AbstractClasspathEntry file inside the ide project in Gradle.

This change involves adding the following code into the writeEntryAttributes method.

// If the dependency is marked as COMPONENT_DEPENDENCY_ATTRIBUTE and not supposed to be exported
 // remove the COMPONENT_DEPENDENCY_ATTRIBUTE so it is not exported
 if (effectiveEntryAttrs.containsKey(COMPONENT_DEPENDENCY_ATTRIBUTE) && !exported) {
  effectiveEntryAttrs.remove(COMPONENT_DEPENDENCY_ATTRIBUTE)
 }

This will give you a new complete method like below

void writeEntryAttributes(Node node) {
        def effectiveEntryAttrs = entryAttributes.findAll { it.value || it.key == COMPONENT_NON_DEPENDENCY_ATTRIBUTE }
        if (!effectiveEntryAttrs) { return }
          if (effectiveEntryAttrs.containsKey(COMPONENT_DEPENDENCY_ATTRIBUTE)
                && effectiveEntryAttrs.containsKey(COMPONENT_NON_DEPENDENCY_ATTRIBUTE)) {
            //For conflicting component dependency entries, the non-dependency loses
            //because it is our default and it means the user has configured something else.
            effectiveEntryAttrs.remove(COMPONENT_NON_DEPENDENCY_ATTRIBUTE)
        }
    // If the dependency is marked as COMPONENT_DEPENDENCY_ATTRIBUTE and not supposed to be exported
  // remove the COMPONENT_DEPENDENCY_ATTRIBUTE so it is not exported
  if (effectiveEntryAttrs.containsKey(COMPONENT_DEPENDENCY_ATTRIBUTE) && !exported) {
   effectiveEntryAttrs.remove(COMPONENT_DEPENDENCY_ATTRIBUTE)
  }
          Node attributesNode = node.children().find { it.name()
== 'attributes' } ?: node.appendNode('attributes')
        effectiveEntryAttrs.each { key, value ->
            attributesNode.appendNode('attribute', [name: key, value: value])
        }
    }

Both ways appear to have resolved the issue, I guess the final question comes down to whether Gradle is supposed to configure eclipse to export test dependencies or not.

I had thought as the default configuration depends on runtime and neither of the test configurations that it shouldn’t export them.


(Luke Daley) #3

So the core issue here is that Gradle is not flagging the test dependencies to not be exported in Eclipse?

If that’s the case, seems like a pretty cut-and-dry defect and Gradle should do this. If you can confirm that’s the problem I’ll raise an issue.


(Adam Charlton) #4

Hi Luke,

I debugged the Gradle code while doing a gradle cleanEclipse eclipse to figure out what piece of code was originally causing the dependency to be marked exported and I believe it is part of the configureEclipseClasspathForWarPlugin method in the EclipseWtpPlugin

doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
 otherProject.eclipse.classpath.file.whenMerged { Classpath classpath ->
  for (entry in classpath.entries) {
   if (entry instanceof AbstractLibrary) {
    // '../' and '/WEB-INF/lib' both seem to be correct (and equivalent) values here
    //this is necessary so that the depended upon projects will have their dependencies
    // deployed to WEB-INF/lib of the main project.
    entry.entryAttributes[AbstractClasspathEntry.COMPONENT_DEPENDENCY_ATTRIBUTE] = '../'
   }
  }
 }
}

The code goes through the dependant projects of a war project and marks all their dependencies as exported, ignoring the exported flag.

The exported flag is present when the entry is checked to be an instance of AbstractLibrary and I believe an extra check should be done there to determine if an entry should be exported or not.

This would give the eclipse build the same functionality as a normal Gradle build, as I’m fairly sure test dependencies are not transitively resolved using the default configuration.

Edit: Confirmed that a new Gradle built with the exported check works and produces the correct .classpath files


(Luke Daley) #5

Thanks for your thorough analysis. Raised as GRADLE-2758.