Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.process.ExecOperations
import org.gradle.process.ExecResult
import org.gradle.process.JavaExecSpec
Expand Down Expand Up @@ -88,6 +89,22 @@ abstract class GroovyPageForkCompileTask extends AbstractCompile {

private ExecOperations execOperations

/**
* The Java runtime the pages are compiled by.
*
* <p>Compilation is forked, and a forked process runs whatever JVM it is given rather than the
* one the project asked for. Left to itself it inherits the JVM running Gradle, so a project
* declaring a toolchain gets its pages compiled by a different Java than everything else it
* builds -- which shows up as an {@code UnsupportedClassVersionError} at the moment a page is
* first rendered, long after the build called itself successful.</p>
*
* <p>Nested rather than internal because this task is cacheable: the Java that did the
* compiling is part of what the result is, and an entry produced by one must not be handed to
* a build asking for another.</p>
*/
@Nested
abstract Property<JavaLauncher> getJavaLauncher()

@OutputDirectory
final DirectoryProperty destinationDirectory

Expand Down Expand Up @@ -143,6 +160,7 @@ abstract class GroovyPageForkCompileTask extends AbstractCompile {
@Override
@CompileDynamic
void execute(JavaExecSpec javaExecSpec) {
javaExecSpec.executable = javaLauncher.get().executablePath.asFile.absolutePath
javaExecSpec.mainClass.set(getCompilerName())
javaExecSpec.setClasspath(getClasspath())

Expand All @@ -161,7 +179,9 @@ abstract class GroovyPageForkCompileTask extends AbstractCompile {
srcDir.get().asFile.canonicalPath,
destinationDirectory.get().asFile.canonicalPath,
tmp.canonicalPath,
targetCompatibility,
// What a page is compiled for follows what it is compiled by,
// unless the build has said otherwise for itself.
targetCompatibility ?: javaLauncher.get().metadata.languageVersion.toString(),
packageName.get() as String,
serverpath.getOrNull() as String,
configFiles,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ import org.gradle.api.file.CopySpec
import org.gradle.api.file.Directory
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetOutput
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.War
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService

import org.grails.gradle.plugin.util.SourceSets

Expand Down Expand Up @@ -69,12 +72,20 @@ class GroovyPagePlugin implements Plugin<Project> {
].findAll { it }
)

// The Java the rest of the project is built with, so that pages are built with it too.
// Absent a toolchain this resolves to the JVM running Gradle, which is what compiling
// pages fell back to before and remains the right answer when nothing else was asked for.
JavaPluginExtension javaExtension = project.extensions.getByType(JavaPluginExtension)
JavaToolchainService toolchains = project.extensions.getByType(JavaToolchainService)
Provider<JavaLauncher> launcher = toolchains.launcherFor(javaExtension.toolchain)

def compileGroovyPages = tasks.register('compileGroovyPages', GroovyPageForkCompileTask) {
it.destinationDirectory.set(destDir)
it.tmpDirPath = getTmpDirPath(project)
it.source = project.layout.projectDirectory.dir('grails-app/views')
it.serverpath.set('/WEB-INF/grails-app/views/')
it.classpath = allClasspath
it.javaLauncher.convention(launcher)
}

def compileWebappGroovyPages = tasks.register('compileWebappGroovyPages', GroovyPageForkCompileTask) {
Expand All @@ -83,6 +94,7 @@ class GroovyPagePlugin implements Plugin<Project> {
it.tmpDirPath = getTmpDirPath(project)
it.serverpath.set('/')
it.classpath = allClasspath
it.javaLauncher.convention(launcher)
}

compileGroovyPages.configure {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.gradle.plugin.views.gsp

import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.testfixtures.ProjectBuilder
import spock.lang.Specification

/**
* Covers which Java compiles a project's pages.
*
* <p>Compilation is forked, and a forked process runs whatever JVM it is given. Left to itself it
* inherits the one running Gradle, so a project that asked for a toolchain had its pages built by a
* different Java from everything else it built. Nothing failed at build time: the mismatch surfaced
* as an {@code UnsupportedClassVersionError} the first time a page was rendered.</p>
*
* @since 8.0
*/
class GroovyPageToolchainSpec extends Specification {

private Project projectWithPages() {
Project project = ProjectBuilder.builder().build()
project.pluginManager.apply('groovy')
project.pluginManager.apply(GroovyPagePlugin)
project
}

private static GroovyPageForkCompileTask compileTask(Project project, String name) {
project.tasks.getByName(name) as GroovyPageForkCompileTask
}

void 'pages are compiled by the Java the project asked for'() {
given:
Project project = projectWithPages()

when: 'a toolchain, as a project pins the Java it is built with'
project.extensions.getByType(JavaPluginExtension)
.toolchain.languageVersion.set(JavaLanguageVersion.of(21))

then: 'and not by whichever Java happens to be running the build'
compileTask(project, 'compileGroovyPages')
.javaLauncher.get().metadata.languageVersion == JavaLanguageVersion.of(21)
}

void 'the pages under src/main/webapp are compiled by it too'() {
given:
Project project = projectWithPages()

when:
project.extensions.getByType(JavaPluginExtension)
.toolchain.languageVersion.set(JavaLanguageVersion.of(21))

then: 'the second of the two compile tasks is no less able to produce an unreadable class'
compileTask(project, 'compileWebappGroovyPages')
.javaLauncher.get().metadata.languageVersion == JavaLanguageVersion.of(21)
}

void 'a project that asked for nothing is built by the Java running the build'() {
given: 'which is what compiling pages fell back to before, and is still the right answer'
Project project = projectWithPages()

expect:
compileTask(project, 'compileGroovyPages').javaLauncher.get().metadata.languageVersion ==
JavaLanguageVersion.of(System.getProperty('java.specification.version'))
}

void 'an application can name the Java for pages alone'() {
given:
Project project = projectWithPages()
project.extensions.getByType(JavaPluginExtension)
.toolchain.languageVersion.set(JavaLanguageVersion.of(21))

when: 'the convention is a default rather than a fixture'
GroovyPageForkCompileTask task = compileTask(project, 'compileGroovyPages')
task.javaLauncher.set(project.extensions.getByType(org.gradle.jvm.toolchain.JavaToolchainService)
.launcherFor { it.languageVersion.set(JavaLanguageVersion.of(21)) })

then:
task.javaLauncher.get().metadata.languageVersion == JavaLanguageVersion.of(21)
}

void 'the Java that did the compiling is part of what the result is'() {
given: 'the task is cacheable, so an entry built by one Java must not be reused by another'
Project project = projectWithPages()

expect: 'declared as an input, which is what keeps the two apart'
GroovyPageForkCompileTask.getMethod('getJavaLauncher')
.isAnnotationPresent(org.gradle.api.tasks.Nested)
}
}
Loading