Platform dependent org.gradle.java.home


(Clarine Chan) #1

Hi, i have a few projects that are built on both linux and windows, and i was wondering if there is a way to have org.gradle.java.home be platform dependent.

What i’m ultimately trying to achieve is have the java version that a project is to be built with be specified and controlled in the project itself with a jdk path for both linux and windows and then have the right one chosen depending on the platform.

I know there is the option to pass it through the command line such as
gradlew -Dorg.gradle.java.home=/path/to/jdk
but i would prefer if i didn’t have to do that as the user would need to know what version of java the project is compatible with.

Another option i’ve come across is to do
compileJava.options.fork = true
compileJava.options.forkOptions.executable=/the/appropriate/jdk/bin/javac
But it seems like i have to set the executable for different tasks such as Test, Javadoc, JavaExec, etc… and i could very easily miss one.

I could also modify gradlew and gradlew.bat of the project to use the appropriate java version but that change could easily be wiped away when the wrappers are regenerated. Unless there is a property i can specify to set the java version that the wrapper would use?

Any ideas/suggestions would be greatly appreciated. Thanks!


(Joshua Street) #2

I think your best bet would be to use the forking option for compile tasks (don’t forget about all compile tasks). That way, you can have Gradle use whatever java version is on your path, and only focus on requiring a specific version for the compilation stages. I’ve done this for projects that still required Java6 (and Java5), or specialized compilers from for Websphere projects, but still wanted to make sure Sonarqube ran (it requires Java7+). It worked well.

And you should not need to fork every java task. Just the ones that need your required compiler (probably the CompileJava tasks and Test tasks).


(Clarine Chan) #3

Going the route of the forking option for compile tasks, i have something like this where unix_java_home is the path to a java 8 jdk. My system JAVA_HOME is a java 7.

tasks.withType(JavaCompile) {
    options.with {
        fork = true
        forkOptions.executable = unix_java_home + '/bin/javac'
    }
}

tasks.withType(Test){
    executable = unix_java_home + '/bin/java'
}

tasks.withType(JavaExec) {
    executable = unix_java_home + '/bin/java'
}

tasks.withType(Javadoc) {
    executable = unix_java_home + '/bin/javadoc'
}

However, i think i’m still missing something because while it works well on some projects, it fails on a project where i’m importing a version of a third party library that is only java 8 compatible. I get a
com/typesafe/config/Config : Unsupported major.minor version 52.0
which makes sense because the version of Config that i’m using is only java 8 compatible while my java_home is a java 7 version.

@jjstreet said not to forget about all compile tasks, so i’m thinking that i’m missing something but i’m not sure what it is…


(Joshua Street) #4

You will have to make sure that whatever version you fork java processes to is at whatever version of java is required for the project to compile with. Using your Jdk7 version as the compiler for tasks on your windows machine is not going to compile your project because its the wrong version expected by the libraries you’re using.

Is there any reason preventing you from being able to use the system-specified version of java on both machines? Forking processes like this is usually only useful for when you have legacy code needing legacy or specialized compilers. It makes no sense forking to Java 8 and not having your system use java 8 by default–unless you have some sort of external requirement to have Java 7 as your system preferred version.


(Clarine Chan) #5

Oh, this is still on linux. I’m testing it on linux first, and the forked java process is indeed a java8.

In regards to not using the system JAVA_HOME, for now at least, i have a mixture of java 7 and java 8 projects and i assume in the future i might have a mix of java 8 and say java 9. So i’m trying to find a way to have the java version info contained in the project itself and not make any assumptions on what the system’s JAVA_HOME is.


(Joshua Street) #6

If the java you fork to is Java 8, there should be no reason why that particular project doesn’t compile… You are forking all JavaCompile tasks, which should include test compilation.

I am not sure why you would see such an error unless you somehow have the JRE version different than the JDK version for that install (highly unlikely).


(Clarine Chan) #7

Here is the entire build.gradle file for a simple helloWorld project that i have. And the error that i’m getting due to the last import statement.
Again, my system JAVA_HOME is intentionally set to java 7 as i’m trying to get the project to build independent of what JAVA_HOME is set to.

Any additional insight would be very much appreciated.

group 'example'
version '1.0-SNAPSHOT'

task wrapper(type: Wrapper) {
  gradleVersion = '2.14.1'
  distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}

apply plugin: 'java'

sourceCompatibility = 1.8

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

buildscript {
    repositories {
        maven {
            url "${my_repo_url}"
        }
    }

    dependencies {
        classpath 'com.typesafe:config:1.3.1' //only java 8 compatible
    }
}

tasks.withType(JavaCompile) {
    doFirst {
        options.with {
            fork = true
            forkOptions.executable = java8_home + '/bin/javac'
        }
    }
}

tasks.withType(Test) {
    executable = java8_home + '/bin/java'
}

tasks.withType(JavaExec) {
    executable = java8_home + '/bin/java'
}

tasks.withType(Javadoc) {
    executable = java8_home + '/bin/javadoc'
}

import com.typesafe.config.Config

Error:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'helloWorld'.
> Could not open proj remapped class cache for dfjwondd3rpiiipc9hxbd1dwj (/home/chanc/.gradle/caches/2.14.1/scripts-remapped/build_dpl2f5izf4xjghqf8ser7pg8f/dfjwondd3rpiiipc9hxbd1dwj/proj7144310334417215016).
   > Could not open proj generic class cache for build file '/home/chanc/dev/test/helloWorld/build.gradle' (/home/chanc/.gradle/caches/2.14.1/scripts/dfjwondd3rpiiipc9hxbd1dwj/proj/proj7144310334417215016).
      > com/typesafe/config/Config : Unsupported major.minor version 52.0

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

(Joshua Street) #8

anything in the buildscript block are dependencies that the buildscript has, not your project. So if you require plugins that are java 8 only, then you can only run the gradle script with a java 8 JRE.


(Clarine Chan) #9

Ahh… that makes sense. Thanks for the input!


(Brian Thomas) #10

I’m trying to address the same issue as @Chanc. What is not clear to me is if fork=true is required when setting org.gradle.java.home to a project specific JDK. I would prefer not to fork for 2 reasons:

  1. when running gradlew from the command line, the output in cmd|bash window ceases when the fork occurs.
  2. It is slower
    I was hoping to use an init script that is under source control, and perhaps specify the init script in gradlew using -I scriptName, but not clear how to set the JDK without forking.

So far the only way I’ve been able to implement use of a project-defined and managed JDK via only git & Gradle is to:

  1. in gradle.properties set properties for JDK maven coordinates for the JDK stored in our local nexus
  2. in build.gradle
  • in buildscript set project path using values from gradle.properties (e.g. $rootDir/tools/openJDK/${jdkName}-${jdkVersion}-${jdkClassifier}.${jdkExt}
  • if isCi=true (i.e. in Jenkins job) then use a path outside the workspace so it doesn’t get wiped by each build.
  • declare our JDK as a dependency using the maven coordinates
  • create a sync task that only runs (i.e. downloads jdk) if ourJdkDir does not exists
  1. in gradlew and gradlew.bat set JAVA_HOME to our jdk path if the directory exists

The first run of gradlew with these changes will cause build.gradle to download the JDK into the rootDir/tools/openJDK but not use it, and any subsequent gradlew command will pick it up because gradlew will see the dir exists and set JAVA_HOME to it.

I’ve been struggling with this for weeks and googling like crazy, even took the Gradle course provided by Gradle, and have not been able to find an Gradle idomatic way to handle a project managed JDK. The Gradle docs elude to using an init script for this purpose, but provide no examples. I would think this would be a common issue.