【问题标题】:Is it possible to compile and test Groovy source files for Java 6 with Gradle 5?是否可以使用 Gradle 5 为 Java 6 编译和测试 Groovy 源文件?
【发布时间】:2019-05-03 09:42:47
【问题描述】:

根据客户要求,我目前停留在 Java 6 上,因此我现在使用的是 Gradle 2.14.1。为了能够将 Gradle 升级到最新版本并仍然针对 Java 6 进行编译和测试,我按照官方用户指南中的说明进行操作 - 请参阅 Groovy Cross Compilation

在我看来,使用上述配置,编译以下示例 Groovy 类应该会导致错误,因为它使用了 Java 8 中引入的类。

import groovy.transform.TypeChecked

@TypeChecked
class SomeGroovyClass {
    Optional<String> someGroovyMethod() {
        return Optional.empty()
    }
}

但不知何故,Gradle 构建成功了。我在这里错过了什么吗?如何使用最新版本的 Gradle 并仍然编译和测试 Groovy 源文件和 Java 6 的 Java 源文件?

如果我将上述类转换为 Java 类,构建会像预期的那样失败。

我还创建了一个小型测试项目来展示我的问题。该项目可以在这里找到——Github

提前致谢。

【问题讨论】:

    标签: java gradle groovy compilation


    【解决方案1】:

    在过去的几年里,我断断续续地生活在类似的情况下。

    为了解决这个问题,我使用以下 build.gradle 文件创建了一个示例迷你项目:

    apply plugin: 'groovy'
    
    repositories { 
      jcenter()
      mavenCentral()
    }
    
    dependencies { 
        compile "org.codehaus.groovy:groovy:2.4.15"
    }
    
    
    
    if (JavaVersion.current() != JavaVersion.VERSION_1_6) {
      // findJava6Jvm is defined in java-versions.gradle
      // and returns a gradle JavaInfo instance.
      def java6Jvm = findJava6Jvm()
    
      if (!rootProject.ext.has('hasPrintedJavaVersionNote')) {
        println "**************** java version notice ****************"
        println "NOTE: the gradle process and the source compilation"
        println "      are using different versions of java:"
        println ""
        println "    gradle process uses:       ${JavaVersion.current()}"
        println "    source complilation uses:  1.6"
        println ""
        println "*****************************************************"
        rootProject.ext.hasPrintedJavaVersionNote = true
      }
    
      sourceCompatibility = JavaVersion.VERSION_1_6
      targetCompatibility = JavaVersion.VERSION_1_6
    
      tasks.withType(AbstractCompile) {
        options.with {
          fork = true
          forkOptions.executable = java6Jvm.javacExecutable
        }
      }
    
      tasks.withType(GroovyCompile) {
        groovyOptions.with {
          fork = true
        }
      }
    
      tasks.withType(Javadoc) {
        executable = java6Jvm.javadocExecutable
      }
    
      tasks.withType(Test) {
        executable = java6Jvm.javaExecutable
      }
    
      tasks.withType(JavaExec) {
        executable = java6Jvm.javaExecutable
      }
    }
    
    
    def findJava6Jvm(Closure extraUsage = null) {
      if (JavaVersion.current().isJava6()) {
        // if we are already using java 6 to launch gradle, just return 
        // the javac for the current jvm
        return org.gradle.internal.jvm.Jvm.current()
      }
    
      def failOnCondition = { condition, msg -> 
        if (condition) {
          println """
          Executing the gradle build with a JDK different from java 1.6
          (i.e. java 7, 8, etc) requires that you provide the build 
          with enough information so that it can still locate a java 1.6
          jdk for source code compilation. 
    
          If the build can not locate a java 6 jdk, you can help it out by 
          specifying one of the following properties: 
    
            JAVA_HOME_6 environment variable (example: \"export JAVA_HOME_6=/usr/lib/jvm/java-6/\")
            JAVA_HOME_6 system property (-DJAVA_HOME_6=<path>)
            JAVA_HOME_6 gradle project property (-PJAVA_HOME_6=<path>)
          """.stripIndent()  
    
          if (extraUsage != null) {
            extraUsage()
          }
          println msg.stripIndent()        
          throw new GradleException("No 1.6.x jdk found!") 
        }
      }
    
      def name = 'JAVA_HOME_6'
      def custom = [System.env[name], System.properties[name], properties[name]].find { it }
      failOnCondition !custom, """
          ERROR: Please set the JAVA_HOME_6 property in one of the above specified
                 ways"""
    
      def jdkDir = file(custom)
      failOnCondition !jdkDir.isDirectory(), """
          ERROR: The configured JAVA_HOME_6 setting:
          
            $custom
          
          does not point to a directory on the local file system.
          Please set this variable to the JAVA_HOME of a 1.6.x
          jdk"""
    
    
      def fs = File.separator
      def jdkJavac = file("$jdkDir${fs}bin${fs}javac").canonicalFile
      if( !jdkJavac.isFile() ) jdkJavac = file( jdkJavac.path + ".exe" )
    
      failOnCondition !jdkJavac.isFile(), """
          ERROR: Could not locate a bin/javac executable file under 
                 the configured JAVA_HOME_6 setting: 
        
            $custom \n"""
    
      def process = [jdkJavac, "-version"].execute()
      process.waitForOrKill(5000)
      def version = process.err.text.readLines().first()
      failOnCondition !version?.contains('1.6.'), """
          ERROR: The configured JAVA_HOME_6 setting:
        
            $custom
        
          points at a non 1.6 jdk, 'java -version' reports $version!"""
    
      // after all the validations pass, reutrn the jdk javac path
      org.gradle.internal.jvm.Jvm.forHome(jdkDir)
    }
    

    我将您问题中的课程放到了一个文件中:

    java-version-experiment ~> tree src/
    src/
    └── main
        └── groovy
            └── com
                └── somepackage
                    └── SomeGroovyClass.groovy
    

    鉴于上述情况,当我使用 java 8 运行我的源代码的 gradle 编译时:

    java-version-experiment ~>  export JAVA_HOME_6=/usr/lib/jvm/java-6_121-oracle
    
    java-version-experiment ~>  setjava java-8-oracle 
    PATH updated - JAVA_HOME=/usr/lib/jvm/java-8-oracle
    
    java-version-experiment ~> gradle -v
    
    ------------------------------------------------------------
    Gradle 4.10.2
    ------------------------------------------------------------
    
    Build time:   2018-09-19 18:10:15 UTC
    Revision:     b4d8d5d170bb4ba516e88d7fe5647e2323d791dd
    
    Kotlin DSL:   1.0-rc-6
    Kotlin:       1.2.61
    Groovy:       2.4.15
    Ant:          Apache Ant(TM) version 1.9.11 compiled on March 23 2018
    JVM:          1.8.0_201 (Oracle Corporation 25.201-b09)
    OS:           Linux 4.18.0-17-generic amd64
    
    
    java-version-experiment ~>  gradle build
    
    > Configure project :
    **************** java version notice ****************
    NOTE: the gradle process and the source compilation
          are using different versions of java:
    
        gradle process uses:       1.8
        source complilation uses:  1.6
    
    *****************************************************
    
    BUILD SUCCESSFUL in 1s
    2 actionable tasks: 2 executed
    
    java-version-experiment ~>  
    

    似乎该类确实是使用 java 6 编译的。在 linux 上,您可以使用类文件上的命令行十六进制编辑器检查这一点:

    ~> od -j 7 -N 1 -t d1 build/classes/groovy/main/com/somepackage/SomeGroovyClass.class 
    0000007   50
    0000010
    ~> 
    

    它实质上从类文件中提取了字节 7(从零开始),其中 50 是我们感兴趣的。相关的 java 版本号是:

    Java 6 uses major version 50
    Java 7 uses major version 51
    Java 8 uses major version 52
    Java 9 uses major version 53
    Java 10 uses major version 54
    Java 11 uses major version 55
    

    换句话说,在我看来,在编译任务上使用forkOptions.executable 是可行的,并且这些类确实是使用 java 6 编译的。

    但是,在我看来,类路径也泄漏了。我的意思是,似乎即使您使用 java 6 可执行文件进行编译,java 8 类路径和 API 也会泄漏到编译过程中。

    就像你说的,上面的编译应该失败了,但没有。我仍然不知道为什么会发生这种情况,更重要的是如何防止类路径泄漏。

    任何 gradle 大师请随时在这里插话,我非常有兴趣深入了解这个问题。

    >

    找到以下内容:

    为 Java 6 或 Java 7 编译和测试

    Groovy 编译器将始终使用与启动 Gradle 相同的 Java 版本执行。您应该将 sourceCompatibility 和 targetCompatibility 设置为 1.6 或 1.7。如果您还有 Java 源文件,则可以按照与 Java 插件相同的步骤来确保使用正确的 Java 编译器。

    在 gradle 文档中:

    https://docs.gradle.org/current/userguide/groovy_plugin.html

    所以这对于 groovy 来说似乎是不可能的。类文件中的第 7 个字节似乎由:

    targetCompatibility = JavaVersion.VERSION_1_6
    

    设置,即即使使用纯 java 8 并设置 targetCompatibility,我们也会在类主版本字节中获得50

    >

    验证这适用于 java 文件。在 src/main/java/com/somepackage/SomeJavaClass.java 下添加了一个 java 文件,其构建文件与上面配置双 vm 编译场景完全相同。

    结果:

    gradle clean build
    
    > Configure project :
    **************** java version notice ****************
    NOTE: the gradle process and the source compilation
          are using different versions of java:
    
        gradle process uses:       1.8
        source complilation uses:  1.6
    
    *****************************************************
    
    > Task :compileJava FAILED
    /home/mbjarland/projects/java-version-experiment/src/main/java/com/somepackage/SomeJavaClass.java:5: cannot find symbol
    symbol  : class Optional
    location: class com.somepackage.SomeJavaClass
        public Optional<String> someJavaMethod() {
               ^
    /home/mbjarland/projects/java-version-experiment/src/main/java/com/somepackage/SomeJavaClass.java:6: cannot find symbol
    symbol  : variable Optional
    location: class com.somepackage.SomeJavaClass
            return Optional.empty();
                   ^
    2 errors
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':compileJava'.
    > Compilation failed with exit code 1; see the compiler error output for details.
    
    BUILD FAILED in 1s
    2 actionable tasks: 2 executed
    

    这是您所期望的。

    经过漫长的探索后得出结论:这适用于预期的 java 文件,不适用于 groovy 文件。

    【讨论】:

    • 你的发现是正确的。 Groovy 编译是由运行 Gradle 的 JVM 版本执行的,这是无法更改的
    猜你喜欢
    • 2017-09-11
    • 1970-01-01
    • 1970-01-01
    • 2013-04-02
    • 2014-04-05
    • 1970-01-01
    • 1970-01-01
    • 2012-02-26
    • 2013-12-04
    相关资源
    最近更新 更多