Problem with running application - classpath too big

Using the application plugin for a project, gradle is building a distZip that simply does not run. I get the following message at the command line in windows:

The input line is too long. The syntax of the command is incorrect.

I’ve tracked down the problem with the input line being too long and it appears that the CLASSPATH is the problem. I’m importing a jar intended for use in a web application because I need access to one of its classes for my reporting app. But I don’t need any of the dependencies of that jar… so my question is, is it possible to import a project dependency as a jar-only dependency, in the same way as you can set up a regular dependency, like so?

runtime ‘javax.servlet.jsp.jstl:jstl-api:1.2@jar’

Yes, you can eliminate all transitive dependencies like that.

Another thing you can do, if you DO need all transitive dependencies, is inform the application plugin to use a “launcher” jar which in turn has the classpath in it’s manifest:

task launcherJar(type: Jar) {
    appendix = "launcher"
    doFirst {
        manifest {
            attributes "Class-Path": configurations.runtime.files.collect {File file-> file.name }.join(" ")
        }
    }
}
  startScripts {
    dependsOn launcherJar
      // clear up the classpath because the launcher jar has it.
    classpath = files(launcherJar.archivePath)
}

Good solution Rolf. You can make it a little more concise though…

task launcherJar(type: Jar) {
    appendix = "launcher"
    manifest {
      attributes "Class-Path": configurations.runtime.files*.name.join(" ")
    }
}
startScripts {
    // clear up the classpath because the launcher jar has it.
    classpath = launcherJar.outputs.files
}

Gradle can work out the task dependencies from that.

Thanks Luke :slight_smile:

Thanks guys! This helps a lot :slight_smile: That said - is there any reason you couldn’t just put the classpath in the main jar and skip the separate launcher? Pardon if that’s a stupid question, I don’t know much about how the manifest works

*put the classpath in the manifest of the main jar I mean

Also - it seems the actual main jar isn’t getting included in the classpath with this solution. Tips?

Have you tried including the output of the “normal” jar task to the “Class-Path” attribute as well?

something like this, from the top of my head:

(configurations.runtime.files + jar.outputs.files)*.name.join(" ")

Instead of creating a separate launcher jar, you could also adjust the “Class-Path” in the manifest of the main jar as well, like you said above.

Your code suggestion above results in a gradle exception:

Yes, Master?
C:\git\code>gradle :app:ISt:dZ -a
  FAILURE: Build failed with an exception.
  * Where:
Build file 'C:\git\code\build.gradle' line: 318
  * What went wrong:
A problem occurred evaluating root project 'code'.
> No such property: name for class: org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
  * Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
  BUILD FAILED
  Total time: 15.6 secs

I’m using gradle 1.0 milestone 9… do I need to update to RC1 for this to work?

my bad, combining file collections with a simple “+” probably doesn’t work. You probably have to construct a list of files yourself before grabbing all filenames (*.name) for the Class-Path

I copied and pasted, but maybe i did it wrong. This is what I have:

task launcherJar(type: Jar) {
  appendix = "launcher"
     manifest {
   attributes "Class-Path": (configurations.runtime.files + jar.outputs.files)*.name.join(" ")
  }
 }
   startScripts {
  // clear up the classpath because the manifest has it.
  classpath = launcherJar.outputs.files
 }

This might not be very clear, but how about:

(configurations.runtime.files << jar.outputs.files.files).flatten()*.name.join(" ")

Type mismatches between runtime.files (Set) and TaskOutput files (FileCollection) should have caused your error.

Ok, that built successfully. now the only problem I see is that Gradle isn’t adding my project dependency jars (itl-1.0.jar, and its dependency, util-1.0.jar) to the lib dir of distZip. Is there another group I have to add for project(…) dependencies?

project('app:IntelliStat') {
 apply plugin:'application'
 version = 1.0
 dependencies {
  compile project(':api:itl')
    compile 'net.sf.jasperreports:jasperreports:4.5.1'
  compile 'org.springframework:spring:2.5.6.SEC01'
    runtime 'commons-cli:commons-cli:1.2'
  runtime 'commons-dbcp:commons-dbcp:1.3'
 }
   task launcherJar(type: Jar) {
 appendix = "launcher"
    manifest {
 attributes "Class-Path": (configurations.runtime.files + jar.outputs.files)*.name.join(" ")
 }
 }
   //jar.manifest {
 // attributes "Class-Path": configurations.runtime.files*.name.join(" ")
 //}
   startScripts {
  // clear up the classpath because the manifest has it.
  classpath = launcherJar.outputs.files
 }
}

I’m not 100% familiar with the subtleties of resolving transitive project dependencies, but maybe the runtime configuration hasn’t been fully configured by the time the launcherJar task is evaluated.

Have you tried surrounding the manifest block with a doLast block?

Or, change your task definition: task launcherJar(type: Jar) << { … }

ok, this:

task launcherJar(type: Jar) << { ... }

gets all the transitive dependencies but for some reason the main jar ends up as an empty jar with nothing but an empty manifest file, and the intellistat-launcher jar doesn’t get added either.

This also gets all the transitive dependencies:

task launcherJar(type: Jar)
{
  appendix = "launcher"
  doLast{
      manifest {
    attributes "Class-Path": configurations.runtime.files*.name.join(" ")
   }
  }
 }

But the IntelliStat-launcher-1.0.jar doesn’t end up in the lib directory (though the classpath references it correctly in the start script). Also, manually checking MANIFEST.MF in the build dir for the launcher in this configuration showed a blank file with no classpath information.

You don’t add any classes to the Jar, so it comes at no surprise that it’s (almost) empty.

Peter: There are two cases I tested above. I think you’re referring to the first one. But I don’t understand why the launcherJar would have any effect on the contents of the jar, since we’re talking about two separate tasks?

I didn’t follow the whole thread. I’m just saying that the Jar generated by ‘launcherJar’ will not contain any classes. If that’s intended, disregard my comment.

For my own project, what i ended up doing was:

a) Have the launcherJar task with the doLast block declared above the dependencies block b) Declare the launcherJar output as a runtime dependency:

runtime launcherJar.outputs.files
  • This means you can revert the Class-Path in the launcherJar task back to just
configurations.runtime.files*.name.join(" ")

Having the launcherJar as a runtime dependency will cause the jar file to end up in the lib folder

Your build file will then look something like this:

project('app:IntelliStat') {
    apply plugin:'application'
    version = 1.0
      task launcherJar(type: Jar) {
  appendix = "launcher"
  doLast {
   manifest {
    attributes "Class-Path": configurations.runtime.files*.name.join(" ")
   }
  }
 }
      dependencies {
        compile project(':api:itl')
        compile 'net.sf.jasperreports:jasperreports:4.5.1'
        compile 'org.springframework:spring:2.5.6.SEC01'
          runtime 'commons-cli:commons-cli:1.2'
        runtime 'commons-dbcp:commons-dbcp:1.3'
    runtime launcherJar.outputs.files
    }
      startScripts {
        // clear up the classpath because the manifest has it.
        classpath = launcherJar.outputs.files
    }
}

If you don’t like having the launcherJar in your runtime classpath, you could consider adding a “launcher” configuration which extends from the runtime configuration, but then you’d have to inform the distZip task about that.

Peter - That is intended, the only thing we’re trying to get in the launcherJar is a manifest with the classpath so that the classpath doesn’t cause the execution script to fail on Windows.

Rolf - that did indeed take care of getting all the requisite jars into the lib directory. However, checking MANIFEST.MF for IntelliStat-launcher-1.0.jar shows an empty file as so:

Manifest-Version: 1.0

This is crazy - get one thing working and something else breaks. I’m going to post my entire script again in its current form in hopes you’ll see something in there that I’m doing wrong.

project('app:IntelliStat') {
 apply plugin:'application'
 version = 1.0
   task launcherJar(type: Jar) {
  appendix = "launcher"
  doLast {
       manifest {
    attributes "Class-Path": configurations.runtime.files*.name.join(" ")
   }
  }
 }
   dependencies {
  compile project(':api:itl')
    compile 'net.sf.jasperreports:jasperreports:4.5.1'
  compile 'org.springframework:spring:2.5.6.SEC01'
    runtime 'commons-cli:commons-cli:1.2'
  runtime 'commons-dbcp:commons-dbcp:1.3'
      runtime launcherJar.outputs.files
 }
   startScripts {
  // clear up the classpath because the manifest has it.
  classpath = launcherJar.outputs.files + jar.outputs.files
 }
   task copyDatasource(type: Copy) {//copy our <datasource>.xml into intellistat-datasource.xml
   from('/datasource/')
  into('src/main/resources/')
  include(datasource + '.xml')
  rename(datasource + '.xml', 'intellistat-datasource.xml')
 }
   processResources.dependsOn ':app:IntelliStat:copyDatasource'
   mainClassName = "net.intellidata.intellistat.ReportRunner"
 jar.baseName = 'intellistat'
 distZip.baseName = 'intellistat'
}