[GRADLE-739] Better support for downloading dependencies with multiple ext types (jar and zip) and classifiers. Created: 11/Nov/09  Updated: 04/Jan/13  Resolved: 09/Sep/11

Status: Resolved
Project: Gradle
Affects Version/s: 0.8
Fix Version/s: 1.0-milestone-5

Type: Improvement
Reporter: Kirk Rasmussen Assignee: Daz DeBoer
Resolution: Fixed Votes: 3

Issue Links:
Duplicate
Duplicates GRADLE-1567 Cannot have more than one version of ... Resolved
Duplicates GRADLE-1622 Gradle Tooling API EclipseModel silen... Resolved

 Description   

The following build script doesn't work 100%. It will either download
com.google.gwt:gwt-dev:1.7.0:linux-libs:zip OR
com.google.gwt:gwt-dev:'1.7.0:linux.

If I comment one of them out then it will download the artifacts from the Maven repo but when they are both present it will only download the first one in the array.

It appears that Gradle and/or Ivy is tricked into thinking it has already downloaded the resources (I assume because they share the same base name) when it has not and skips it.

If I do them one at a time (i.e. comment one, run gradle, comment the other, run gradle) then it works fine:

ls ~/.gradle/cache/com.google.gwt/gwt-dev/

ivy-1.7.0.xml ivy-1.7.0.xml.original ivydata-1.7.0.properties jars zips

repositories

{ mavenCentral() }

configurations

{ gwt }

dependencies

{ compile ( [group: 'com.google.gwt', name: 'gwt-servlet', version: '1.7.0'], [group: 'com.google.gwt', name: 'gwt-user', version: '1.7.0'], ) gwt ( [group: 'com.google.gwt', name: 'gwt-dev', version: '1.7.0', classifier: 'linux-libs', ext: 'zip'], [group: 'com.google.gwt', name: 'gwt-dev', version: '1.7.0', classifier: 'linux'], [group: 'com.google.gwt', name: 'gwt-user', version: '1.7.0'] ) }

 Comments   
Comment by Steve Ebersole [ 11/Nov/09 ]

Is this the same underlying issue I am seeing with regard to classifiers? If I try to declare dependencies on
(1) [group: 'org.infinispan', name: 'infinispan-core', version: '4.0.0.BETA2']
(2) [group: 'org.infinispan', name: 'infinispan-core', version: '4.0.0.BETA2', classifier: 'tests']

Only one (the first) gets added to the proper classpaths/configurations.

Comment by Hans Dockter [ 12/Nov/09 ]

From a Gradle point of view the following happens. Two modules are added which have the same business key, as they refer to the same module. As we are using sets, the second is not added as an element with the same key already exists. This behavior does not make much sense.

But there is a solution for your problem. In the Gradle/Ivy world a module can have multiple artifacts. The notion of a module is more or less equivalent to an ivy.xml/pom.xml. In the Maven world a module has exactly one artifact (+ classifiers) which has the same name as the module. Our normal dependency notation supports this kind of situation. What you can do in your case is:

dependencies {
   compile("com.google.gwt:gwt-dev:1.7.0") {
      artifact {
         name = 'gwt-dev'
	 type = 'zip'
	 classifier = 'linux-libs' 
      }
      artifact {
         name = 'gwt-dev'
	 type = 'jar'
	 classifier = 'linux' 
      }
   }
}

It is not a perfect solution. We have to think about how to provide a more convenient solution for the typical maven repository situation with multiple classifier jars per module.

Comment by Steve Ebersole [ 12/Nov/09 ]

For mine though I need them in separate configurations:

dependencies {
    infinispanVersion = '4.0.0.BETA2'
    compile (
            this.project(':hibernate-core').sourceSets.main.runtimeClasspath,
            [group: 'org.infinispan', name: 'infinispan-core', version: infinispanVersion]
    )
    testCompile (
            this.project(':hibernate-testing').sourceSets.main.runtimeClasspath,
            [group: 'org.infinispan', name: 'infinispan-core', version: infinispanVersion, classifier: 'tests']
    )
    testRuntime (
            ...
    )
}
Comment by Kirk Rasmussen [ 12/Nov/09 ]

Thanks Hans I'll give that a try. I did find out for sure that Ivy was not the problem. Like you said, adding extra artifacts settings to the dependency does the trick (tested against Ivy 2.1.0 final):

ivy.xml
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
    <info organisation="apache" module="hello-ivy"/>
    <dependencies>

        <dependency org="com.google.gwt" name="gwt-servlet" rev="1.7.0"/>
        <dependency org="com.google.gwt" name="gwt-user" rev="1.7.0"/>

        <dependency org="com.google.gwt" name="gwt-dev" rev="1.7.0">
        	<artifact name="gwt-dev" type="jar" e:classifier="linux" />
        	<artifact name="gwt-dev" type="zip" e:classifier="linux-libs" />
        </dependency>
    </dependencies>
</ivy-module>
build.xml
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="hello-ivy" default="resolve">
    
    <target name="resolve" description="--> retrieve dependencies with ivy">
        <ivy:retrieve />
    </target>

</project>

Comment by Kirk Rasmussen [ 12/Nov/09 ]

Thanks Hans that works perfectly!

BTW in case anyone is wondering I split 'compile' and 'gwt' classpaths because 'gwt' is used for launching the GWT compiler and hosted mode processes.

dependencies {
  compile (
    [group: 'com.google.gwt', name: 'gwt-servlet', version: '1.7.0'],
    [group: 'com.google.gwt', name: 'gwt-user', version: '1.7.0'],
  )
  gwt (
    [group: 'com.google.gwt', name: 'gwt-user', version: '1.7.0']
  )
  gwt('com.google.gwt:gwt-dev:1.7.0') {
    artifact {
      name = 'gwt-dev'
	  type = 'jar'
	  classifier = 'linux' 
    }
    artifact {
      name = 'gwt-dev'
      type = 'zip'
      classifier = 'linux-libs' 
    }
  }
}
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

task gwtCompile(type: GwtCompileTask) {
  gwtBuildDir = mkdir(buildDir, 'gwt')
  module = 'flexnet.compliance.web.gwt.ComplianceApp'
  gwtClasspath = configurations.gwt.asPath
  sourcepath = 'src/main/java'
  compileClasspath = configurations.compile.asPath
} 

class GwtCompileTask extends DefaultTask {
  def File gwtBuildDir
  def String module
  def String gwtClasspath
  def String sourcepath
  def String compileClasspath

  @TaskAction
  def compile() {
    println "Compiling GWT module: $module"
    
    ant.java(classname:'com.google.gwt.dev.Compiler',
        failOnError: 'true',
        fork: 'true') {
        jvmarg(value: '-Xmx256M')
      arg(line: '-war ' + gwtBuildDir)
      arg(line: '-logLevel INFO')
      arg(line: '-style PRETTY')
      arg(value: module)
      classpath {
        pathElement(location: sourcepath)
        pathElement(path: gwtClasspath)
        pathElement(path: compileClasspath)      
      }
    }
  }
}  

Comment by Hans Dockter [ 16/Nov/09 ]

@Steve: A work around for your use case would be to declare:

testCompile ([group: 'org.infinispan', name: 'infinispan-core', version: infinispanVersion) {
   artifact {
      name = "infinispan-core"
      type = 'jar'
   }
   artifact {
      name = "infinispan-core"
      classifier = 'test'
      type = 'jar'
   }
}

The dependency defined in the extending configuration overrides equal dependencies in the extended configuration. Not that I'm saying that this is a nice solution. But at least there is a way until we come up with something better.

Comment by Steve Ebersole [ 16/Nov/09 ]

@Hans: gives me error. I have:

    testCompile (
            this.project(':hibernate-testing').sourceSets.main.runtimeClasspath,
            [group: 'org.infinispan', name: 'infinispan-core', version: infinispanVersion] {
                artifact {
                    name = "infinispan-core"
                    type = 'jar'
                }
                artifact {
                    name = "infinispan-core"
                    classifier = 'test'
                    type = 'jar'
                }
            }
    )

Which gives me:

Cause: No signature of method: java.util.LinkedHashMap.call() is applicable for argument types: (build_gradle_5fd072d129293cdc26ce5b167cb05041$_run_closure1_closure5) values: [build_gradle_5fd072d129293cdc26ce5b167cb05041$_run_closure1_closure5@19bc716]
Comment by Steve Ebersole [ 16/Nov/09 ]

Oh, I think just a mix up wrt parens and braces. Your example had an unbalanced brace "([group: 'org.infinispan'..." with no matching "]" so I just tried what I though you meant. I changed it instead to:

    compile (
            this.project(':hibernate-core').sourceSets.main.runtimeClasspath,
            [group: 'org.infinispan', name: 'infinispan-core', version: infinispanVersion]
    )
    testCompile (
            this.project(':hibernate-testing').sourceSets.main.runtimeClasspath
    )
    testCompile (
            [group: 'org.infinispan', name: 'infinispan-core', version: infinispanVersion]
    ) {
        artifact {
            name = "infinispan-core"
            type = 'jar'
        }
        artifact {
            name = "infinispan-core"
            classifier = 'test'
            type = 'jar'
        }
    }

Which gives:

Execution failed for task ':hibernate-infinispan:compileTestJava'.
Cause: Could not resolve all dependencies for configuration 'testCompile':
    - download failed: org.infinispan#infinispan-core;4.0.0.BETA2!infinispan-core.jar

Oddly, it was able to resolve that artifact as part of the earlier 'compile' configuration...

Comment by Steve Ebersole [ 16/Nov/09 ]

Hmmm, "infinispan-core.jar"... It's missing the version in the name of the jar it is trying to look for if I read that error message correctly.

Comment by Steve Ebersole [ 16/Nov/09 ]

I even tried using customName within the nested artifact definitions.

Without the 'name', 'type', etc attributes you'll get NPE from at org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact.hashCode(DefaultDependencyArtifact.java:76)

With those attributes, customName does not seem to have any effect.

Comment by Hans Dockter [ 16/Nov/09 ]

Sorry for not attaching running code. The following example works for me:

usePlugin 'java'

repositories {
   mavenCentral()
   mavenRepo(urls: 'http://repository.jboss.com/maven2')
   mavenRepo(urls: 'http://snapshots.jboss.org/maven2')
}

dependencies {
   compile("org.infinispan:infinispan-core:4.0.0.BETA2") {
      artifact {
         name = 'infinispan-core'
	     type = 'jar'
      }
   }
   testCompile("org.infinispan:infinispan-core:4.0.0.BETA2") {
      artifact {
         name = 'infinispan-core'
	     type = 'jar'
      }
      artifact {
         name = 'infinispan-core'
         classifier = 'tests'
	     type = 'jar'
      }
   }
}

task show << {
   println configurations.compile.files
   println configurations.testCompile.files
}
Comment by Hans Dockter [ 16/Nov/09 ]

@Steve: In your example the classifier name must be tests not test.

Comment by Jörg Schreiner [ 17/Feb/11 ]

I have the same use case as Steve and I hope it will soon be fixed.

Comment by Pivotal Tracker Integration [ 04/Aug/11 ]

A Pivotal Tracker story has been created for this Issue: http://www.pivotaltracker.com/story/show/16643687

Generated at Wed Jun 30 11:37:37 CDT 2021 using Jira 8.4.2#804003-sha1:d21414fc212e3af190e92c2d2ac41299b89402cf.