From cac4a8240029506dea9f4b9c8f775c9d4f8fb449 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 16 Jun 2023 14:41:07 +0300 Subject: [PATCH 01/35] Run `gradle init` --- gradle-twirl/.gitattributes | 9 + gradle-twirl/.gitignore | 5 + gradle-twirl/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 62076 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + gradle-twirl/gradlew | 245 ++++++++++++++++++ gradle-twirl/gradlew.bat | 92 +++++++ gradle-twirl/plugin/build.gradle.kts | 58 +++++ .../GradleTwirlPluginFunctionalTest.java | 56 ++++ .../play/twirl/gradle/GradleTwirlPlugin.java | 19 ++ .../twirl/gradle/GradleTwirlPluginTest.java | 24 ++ gradle-twirl/settings.gradle.kts | 12 + 12 files changed, 532 insertions(+) create mode 100644 gradle-twirl/.gitattributes create mode 100644 gradle-twirl/.gitignore create mode 100644 gradle-twirl/gradle.properties create mode 100644 gradle-twirl/gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle-twirl/gradle/wrapper/gradle-wrapper.properties create mode 100755 gradle-twirl/gradlew create mode 100644 gradle-twirl/gradlew.bat create mode 100644 gradle-twirl/plugin/build.gradle.kts create mode 100644 gradle-twirl/plugin/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java create mode 100644 gradle-twirl/plugin/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java create mode 100644 gradle-twirl/plugin/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java create mode 100644 gradle-twirl/settings.gradle.kts diff --git a/gradle-twirl/.gitattributes b/gradle-twirl/.gitattributes new file mode 100644 index 00000000..097f9f98 --- /dev/null +++ b/gradle-twirl/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/gradle-twirl/.gitignore b/gradle-twirl/.gitignore new file mode 100644 index 00000000..1b6985c0 --- /dev/null +++ b/gradle-twirl/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/gradle-twirl/gradle.properties b/gradle-twirl/gradle.properties new file mode 100644 index 00000000..18f452c7 --- /dev/null +++ b/gradle-twirl/gradle.properties @@ -0,0 +1,6 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.parallel=true +org.gradle.caching=true + diff --git a/gradle-twirl/gradle/wrapper/gradle-wrapper.jar b/gradle-twirl/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..c1962a79e29d3e0ab67b14947c167a862655af9b GIT binary patch literal 62076 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 literal 0 HcmV?d00001 diff --git a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..37aef8d3 --- /dev/null +++ b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradle-twirl/gradlew b/gradle-twirl/gradlew new file mode 100755 index 00000000..aeb74cbb --- /dev/null +++ b/gradle-twirl/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradle-twirl/gradlew.bat b/gradle-twirl/gradlew.bat new file mode 100644 index 00000000..93e3f59f --- /dev/null +++ b/gradle-twirl/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/gradle-twirl/plugin/build.gradle.kts b/gradle-twirl/plugin/build.gradle.kts new file mode 100644 index 00000000..7207d6fa --- /dev/null +++ b/gradle-twirl/plugin/build.gradle.kts @@ -0,0 +1,58 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Gradle plugin project to get you started. + * For more details take a look at the Writing Custom Plugins chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.1.1/userguide/custom_plugins.html + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + // Apply the Java Gradle plugin development plugin to add support for developing Gradle plugins + `java-gradle-plugin` +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +testing { + suites { + // Configure the built-in test suite + val test by getting(JvmTestSuite::class) { + // Use JUnit Jupiter test framework + useJUnitJupiter("5.9.1") + } + + // Create a new test suite + val functionalTest by registering(JvmTestSuite::class) { + dependencies { + // functionalTest test suite depends on the production code in tests + implementation(project()) + } + + targets { + all { + // This test suite should run after the built-in test suite has run its tests + testTask.configure { shouldRunAfter(test) } + } + } + } + } +} + +gradlePlugin { + // Define the plugin + val greeting by plugins.creating { + id = "play.twirl.gradle.greeting" + implementationClass = "play.twirl.gradle.GradleTwirlPlugin" + } +} + +gradlePlugin.testSourceSets.add(sourceSets["functionalTest"]) + +tasks.named("check") { + // Include functionalTest as part of the check lifecycle + dependsOn(testing.suites.named("functionalTest")) +} diff --git a/gradle-twirl/plugin/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java b/gradle-twirl/plugin/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java new file mode 100644 index 00000000..b0c52e25 --- /dev/null +++ b/gradle-twirl/plugin/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java @@ -0,0 +1,56 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package play.twirl.gradle; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.io.FileWriter; +import java.nio.file.Files; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.*; + +/** + * A simple functional test for the 'play.twirl.gradle.greeting' plugin. + */ +class GradleTwirlPluginFunctionalTest { + @TempDir + File projectDir; + + private File getBuildFile() { + return new File(projectDir, "build.gradle"); + } + + private File getSettingsFile() { + return new File(projectDir, "settings.gradle"); + } + + @Test void canRunTask() throws IOException { + writeString(getSettingsFile(), ""); + writeString(getBuildFile(), + "plugins {" + + " id('play.twirl.gradle.greeting')" + + "}"); + + // Run the build + GradleRunner runner = GradleRunner.create(); + runner.forwardOutput(); + runner.withPluginClasspath(); + runner.withArguments("greeting"); + runner.withProjectDir(projectDir); + BuildResult result = runner.build(); + + // Verify the result + assertTrue(result.getOutput().contains("Hello from plugin 'play.twirl.gradle.greeting'")); + } + + private void writeString(File file, String string) throws IOException { + try (Writer writer = new FileWriter(file)) { + writer.write(string); + } + } +} diff --git a/gradle-twirl/plugin/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java b/gradle-twirl/plugin/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java new file mode 100644 index 00000000..eb030a5e --- /dev/null +++ b/gradle-twirl/plugin/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java @@ -0,0 +1,19 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package play.twirl.gradle; + +import org.gradle.api.Project; +import org.gradle.api.Plugin; + +/** + * A simple 'hello world' plugin. + */ +public class GradleTwirlPlugin implements Plugin { + public void apply(Project project) { + // Register a task + project.getTasks().register("greeting", task -> { + task.doLast(s -> System.out.println("Hello from plugin 'play.twirl.gradle.greeting'")); + }); + } +} diff --git a/gradle-twirl/plugin/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java b/gradle-twirl/plugin/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java new file mode 100644 index 00000000..aa524eb5 --- /dev/null +++ b/gradle-twirl/plugin/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java @@ -0,0 +1,24 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package play.twirl.gradle; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.gradle.api.Project; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; + +/** + * A simple unit test for the 'play.twirl.gradle.greeting' plugin. + */ +class GradleTwirlPluginTest { + @Test void pluginRegistersATask() { + // Create a test project and apply the plugin + Project project = ProjectBuilder.builder().build(); + project.getPlugins().apply("play.twirl.gradle.greeting"); + + // Verify the result + assertNotNull(project.getTasks().findByName("greeting")); + } +} diff --git a/gradle-twirl/settings.gradle.kts b/gradle-twirl/settings.gradle.kts new file mode 100644 index 00000000..847b5b75 --- /dev/null +++ b/gradle-twirl/settings.gradle.kts @@ -0,0 +1,12 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.1.1/userguide/multi_project_builds.html + * This project uses @Incubating APIs which are subject to change. + */ + +rootProject.name = "gradle-twirl" +include("plugin") From a4029c0dc14bfa1eeb8376e7518134f725b64122 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 16 Jun 2023 14:45:19 +0300 Subject: [PATCH 02/35] Move `plugin/*` to parent folder --- gradle-twirl/{plugin => }/build.gradle.kts | 2 +- gradle-twirl/settings.gradle.kts | 1 - .../java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java | 0 .../src/main/java/play/twirl/gradle/GradleTwirlPlugin.java | 0 .../src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java | 0 5 files changed, 1 insertion(+), 2 deletions(-) rename gradle-twirl/{plugin => }/build.gradle.kts (96%) rename gradle-twirl/{plugin => }/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java (100%) rename gradle-twirl/{plugin => }/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java (100%) rename gradle-twirl/{plugin => }/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java (100%) diff --git a/gradle-twirl/plugin/build.gradle.kts b/gradle-twirl/build.gradle.kts similarity index 96% rename from gradle-twirl/plugin/build.gradle.kts rename to gradle-twirl/build.gradle.kts index 7207d6fa..d1804ef3 100644 --- a/gradle-twirl/plugin/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -35,7 +35,7 @@ testing { targets { all { // This test suite should run after the built-in test suite has run its tests - testTask.configure { shouldRunAfter(test) } + testTask.configure { shouldRunAfter(test) } } } } diff --git a/gradle-twirl/settings.gradle.kts b/gradle-twirl/settings.gradle.kts index 847b5b75..03b6c176 100644 --- a/gradle-twirl/settings.gradle.kts +++ b/gradle-twirl/settings.gradle.kts @@ -9,4 +9,3 @@ */ rootProject.name = "gradle-twirl" -include("plugin") diff --git a/gradle-twirl/plugin/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java similarity index 100% rename from gradle-twirl/plugin/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java rename to gradle-twirl/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java diff --git a/gradle-twirl/plugin/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java similarity index 100% rename from gradle-twirl/plugin/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java rename to gradle-twirl/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java diff --git a/gradle-twirl/plugin/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java similarity index 100% rename from gradle-twirl/plugin/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java rename to gradle-twirl/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java From a5b0022c2eb13c21384f20df55c2d9813b2ba02b Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 16 Jun 2023 14:58:58 +0300 Subject: [PATCH 03/35] Rename plugin id to `com.playframework.twirl` Rename implementation class to `TwirlPlugin` --- gradle-twirl/build.gradle.kts | 4 ++-- ...lTest.java => TwirlPluginFunctionalTest.java} | 16 ++++++++-------- .../{GradleTwirlPlugin.java => TwirlPlugin.java} | 4 ++-- ...TwirlPluginTest.java => TwirlPluginTest.java} | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) rename gradle-twirl/src/functionalTest/java/play/twirl/gradle/{GradleTwirlPluginFunctionalTest.java => TwirlPluginFunctionalTest.java} (82%) rename gradle-twirl/src/main/java/play/twirl/gradle/{GradleTwirlPlugin.java => TwirlPlugin.java} (81%) rename gradle-twirl/src/test/java/play/twirl/gradle/{GradleTwirlPluginTest.java => TwirlPluginTest.java} (76%) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index d1804ef3..28bc4607 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -45,8 +45,8 @@ testing { gradlePlugin { // Define the plugin val greeting by plugins.creating { - id = "play.twirl.gradle.greeting" - implementationClass = "play.twirl.gradle.GradleTwirlPlugin" + id = "com.playframework.twirl" + implementationClass = "play.twirl.gradle.TwirlPlugin" } } diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java similarity index 82% rename from gradle-twirl/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java rename to gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index b0c52e25..5b74c7b6 100644 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/GradleTwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -3,21 +3,21 @@ */ package play.twirl.gradle; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.io.Writer; -import java.io.FileWriter; -import java.nio.file.Files; -import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import static org.junit.jupiter.api.Assertions.*; /** - * A simple functional test for the 'play.twirl.gradle.greeting' plugin. + * A simple functional test for the 'com.playframework.twirl' plugin. */ -class GradleTwirlPluginFunctionalTest { +class TwirlPluginFunctionalTest { @TempDir File projectDir; @@ -33,7 +33,7 @@ private File getSettingsFile() { writeString(getSettingsFile(), ""); writeString(getBuildFile(), "plugins {" + - " id('play.twirl.gradle.greeting')" + + " id('com.playframework.twirl')" + "}"); // Run the build @@ -45,7 +45,7 @@ private File getSettingsFile() { BuildResult result = runner.build(); // Verify the result - assertTrue(result.getOutput().contains("Hello from plugin 'play.twirl.gradle.greeting'")); + assertTrue(result.getOutput().contains("Hello from plugin 'com.playframework.twirl'")); } private void writeString(File file, String string) throws IOException { diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java similarity index 81% rename from gradle-twirl/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java rename to gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index eb030a5e..e2d9acb7 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/GradleTwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -9,11 +9,11 @@ /** * A simple 'hello world' plugin. */ -public class GradleTwirlPlugin implements Plugin { +public class TwirlPlugin implements Plugin { public void apply(Project project) { // Register a task project.getTasks().register("greeting", task -> { - task.doLast(s -> System.out.println("Hello from plugin 'play.twirl.gradle.greeting'")); + task.doLast(s -> System.out.println("Hello from plugin 'com.playframework.twirl'")); }); } } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java similarity index 76% rename from gradle-twirl/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java rename to gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index aa524eb5..2ccb605d 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/GradleTwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -10,13 +10,13 @@ import org.junit.jupiter.api.Test; /** - * A simple unit test for the 'play.twirl.gradle.greeting' plugin. + * A simple unit test for the 'com.playframework.twirl' plugin. */ -class GradleTwirlPluginTest { +class TwirlPluginTest { @Test void pluginRegistersATask() { // Create a test project and apply the plugin Project project = ProjectBuilder.builder().build(); - project.getPlugins().apply("play.twirl.gradle.greeting"); + project.getPlugins().apply("com.playframework.twirl"); // Verify the result assertNotNull(project.getTasks().findByName("greeting")); From 2b869cacfb0f980d7f204ae24416824d72c9bf4d Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 16 Jun 2023 15:08:49 +0300 Subject: [PATCH 04/35] Temporary ignore the headers check --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 34332fa4..11620c3e 100644 --- a/build.sbt +++ b/build.sbt @@ -43,6 +43,7 @@ lazy val twirl = project (Compile / headerSources) ++= ((baseDirectory.value ** ("*.properties" || "*.md" || "*.sbt" || "*.scala.html")) --- (baseDirectory.value ** "target" ** "*") + --- (baseDirectory.value ** "gradle-twirl" ** "*") // TODO: add headers to gradle plugin sources --- (baseDirectory.value / "docs" ** "*")).get ++ (baseDirectory.value / "project" ** "*.scala" --- (baseDirectory.value ** "target" ** "*")).get ) From e43de02fdc2955df7ff58c1a6f91fd47addab8e3 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 16 Jun 2023 16:59:09 +0300 Subject: [PATCH 05/35] Enable Java Format check for Gradle Plugin --- gradle-twirl/build.gradle.kts | 7 ++ .../gradle/TwirlPluginFunctionalTest.java | 69 +++++++++---------- .../java/play/twirl/gradle/TwirlPlugin.java | 22 +++--- .../play/twirl/gradle/TwirlPluginTest.java | 19 +++-- 4 files changed, 60 insertions(+), 57 deletions(-) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 28bc4607..eb72964b 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -10,6 +10,7 @@ plugins { // Apply the Java Gradle plugin development plugin to add support for developing Gradle plugins `java-gradle-plugin` + id("com.diffplug.spotless") version "6.19.0" } repositories { @@ -52,6 +53,12 @@ gradlePlugin { gradlePlugin.testSourceSets.add(sourceSets["functionalTest"]) +spotless { + java { + googleJavaFormat() + } +} + tasks.named("check") { // Include functionalTest as part of the check lifecycle dependsOn(testing.suites.named("functionalTest")) diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index 5b74c7b6..a68926cb 100644 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -14,43 +14,38 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -/** - * A simple functional test for the 'com.playframework.twirl' plugin. - */ +/** A simple functional test for the 'com.playframework.twirl' plugin. */ class TwirlPluginFunctionalTest { - @TempDir - File projectDir; - - private File getBuildFile() { - return new File(projectDir, "build.gradle"); - } - - private File getSettingsFile() { - return new File(projectDir, "settings.gradle"); - } - - @Test void canRunTask() throws IOException { - writeString(getSettingsFile(), ""); - writeString(getBuildFile(), - "plugins {" + - " id('com.playframework.twirl')" + - "}"); - - // Run the build - GradleRunner runner = GradleRunner.create(); - runner.forwardOutput(); - runner.withPluginClasspath(); - runner.withArguments("greeting"); - runner.withProjectDir(projectDir); - BuildResult result = runner.build(); - - // Verify the result - assertTrue(result.getOutput().contains("Hello from plugin 'com.playframework.twirl'")); - } - - private void writeString(File file, String string) throws IOException { - try (Writer writer = new FileWriter(file)) { - writer.write(string); - } + @TempDir File projectDir; + + private File getBuildFile() { + return new File(projectDir, "build.gradle"); + } + + private File getSettingsFile() { + return new File(projectDir, "settings.gradle"); + } + + @Test + void canRunTask() throws IOException { + writeString(getSettingsFile(), ""); + writeString(getBuildFile(), "plugins {" + " id('com.playframework.twirl')" + "}"); + + // Run the build + GradleRunner runner = GradleRunner.create(); + runner.forwardOutput(); + runner.withPluginClasspath(); + runner.withArguments("greeting"); + runner.withProjectDir(projectDir); + BuildResult result = runner.build(); + + // Verify the result + assertTrue(result.getOutput().contains("Hello from plugin 'com.playframework.twirl'")); + } + + private void writeString(File file, String string) throws IOException { + try (Writer writer = new FileWriter(file)) { + writer.write(string); } + } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index e2d9acb7..c6062ce4 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -3,17 +3,19 @@ */ package play.twirl.gradle; -import org.gradle.api.Project; import org.gradle.api.Plugin; +import org.gradle.api.Project; -/** - * A simple 'hello world' plugin. - */ +/** A simple 'hello world' plugin. */ public class TwirlPlugin implements Plugin { - public void apply(Project project) { - // Register a task - project.getTasks().register("greeting", task -> { - task.doLast(s -> System.out.println("Hello from plugin 'com.playframework.twirl'")); - }); - } + public void apply(Project project) { + // Register a task + project + .getTasks() + .register( + "greeting", + task -> { + task.doLast(s -> System.out.println("Hello from plugin 'com.playframework.twirl'")); + }); + } } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index 2ccb605d..adf1ca20 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -9,16 +9,15 @@ import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; -/** - * A simple unit test for the 'com.playframework.twirl' plugin. - */ +/** A simple unit test for the 'com.playframework.twirl' plugin. */ class TwirlPluginTest { - @Test void pluginRegistersATask() { - // Create a test project and apply the plugin - Project project = ProjectBuilder.builder().build(); - project.getPlugins().apply("com.playframework.twirl"); + @Test + void pluginRegistersATask() { + // Create a test project and apply the plugin + Project project = ProjectBuilder.builder().build(); + project.getPlugins().apply("com.playframework.twirl"); - // Verify the result - assertNotNull(project.getTasks().findByName("greeting")); - } + // Verify the result + assertNotNull(project.getTasks().findByName("greeting")); + } } From 915f8114e81aba61b4a130bcfc44d03a48aeebc9 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 23 Jun 2023 18:08:20 +0300 Subject: [PATCH 06/35] Enable License Header check for Gradle Plugin --- gradle-twirl/build.gradle.kts | 22 +++++++++++++------ gradle-twirl/gradle.properties | 3 +-- gradle-twirl/settings.gradle.kts | 10 ++------- .../gradle/TwirlPluginFunctionalTest.java | 4 ++-- .../java/play/twirl/gradle/TwirlPlugin.java | 4 ++-- .../play/twirl/gradle/TwirlPluginTest.java | 4 ++-- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index eb72964b..dfa84fd3 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -1,11 +1,6 @@ /* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Gradle plugin project to get you started. - * For more details take a look at the Writing Custom Plugins chapter in the Gradle - * User Manual available at https://docs.gradle.org/8.1.1/userguide/custom_plugins.html - * This project uses @Incubating APIs which are subject to change. - */ +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ plugins { // Apply the Java Gradle plugin development plugin to add support for developing Gradle plugins @@ -53,9 +48,22 @@ gradlePlugin { gradlePlugin.testSourceSets.add(sourceSets["functionalTest"]) +val headerLicense = "Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " +val headerLicenseHash = "# $headerLicense" +val headerLicenseJava = "/*\n* $headerLicense\n*/" + spotless { java { googleJavaFormat() + licenseHeader(headerLicenseJava) + } + kotlinGradle { + licenseHeader(headerLicenseJava, "[^/*]") + } + format("properties") { + target("**/*.properties") + targetExclude("gradle/**") + licenseHeader(headerLicenseHash, "[^#]") } } diff --git a/gradle-twirl/gradle.properties b/gradle-twirl/gradle.properties index 18f452c7..313c5779 100644 --- a/gradle-twirl/gradle.properties +++ b/gradle-twirl/gradle.properties @@ -1,5 +1,4 @@ -# This file was generated by the Gradle 'init' task. -# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties +# Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. org.gradle.parallel=true org.gradle.caching=true diff --git a/gradle-twirl/settings.gradle.kts b/gradle-twirl/settings.gradle.kts index 03b6c176..b8b5e124 100644 --- a/gradle-twirl/settings.gradle.kts +++ b/gradle-twirl/settings.gradle.kts @@ -1,11 +1,5 @@ /* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/8.1.1/userguide/multi_project_builds.html - * This project uses @Incubating APIs which are subject to change. - */ +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ rootProject.name = "gradle-twirl" diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index a68926cb..f4e55d1c 100644 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -1,6 +1,6 @@ /* - * This Java source file was generated by the Gradle 'init' task. - */ +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ package play.twirl.gradle; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index c6062ce4..13809be0 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -1,6 +1,6 @@ /* - * This Java source file was generated by the Gradle 'init' task. - */ +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ package play.twirl.gradle; import org.gradle.api.Plugin; diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index adf1ca20..aea245ee 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -1,6 +1,6 @@ /* - * This Java source file was generated by the Gradle 'init' task. - */ +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ package play.twirl.gradle; import static org.junit.jupiter.api.Assertions.assertNotNull; From c74895c756a1c28a5f97427f26745ede21ddf4a0 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Mon, 19 Jun 2023 14:20:09 +0300 Subject: [PATCH 07/35] Enable CI for Gradle Plugin --- .github/workflows/build-test.yml | 13 ++++++- .github/workflows/publish.yml | 22 +++++++++++- build.sbt | 2 +- gradle-twirl/build.gradle.kts | 34 +++++++++++++++++-- .../gradle/wrapper/gradle-wrapper.properties | 1 + .../gradle/TwirlPluginFunctionalTest.java | 2 +- .../play/twirl/gradle/TwirlPluginTest.java | 2 +- scripts/test-code.sh | 1 + 8 files changed, 69 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2421c024..494d7751 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -13,11 +13,21 @@ concurrency: cancel-in-progress: true jobs: + # Run Gradle Wrapper Validation to verify the wrapper's checksum + gradle-validation: + name: Gradle Wrapper + uses: playframework/.github/.github/workflows/gradle-wrapper-validation.yml@v3 + check-code-style: name: Code Style + needs: + - "gradle-validation" uses: playframework/.github/.github/workflows/cmd.yml@v3 with: - cmd: sbt validateCode + gradle-build-root: gradle-twirl + cmd: | + sbt validateCode + cd gradle-twirl && ./gradlew clean spotlessCheck --no-daemon check-binary-compatibility: name: Binary Compatibility @@ -42,6 +52,7 @@ jobs: java: 17, 11 scala: 2.12.x, 2.13.x, 3.x cmd: scripts/test-code.sh + gradle-build-root: gradle-twirl finish: name: Finish diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 92e6f544..ffeb952e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,12 +4,32 @@ on: push: branches: # Snapshots - main + - gradle # TODO: remove this line tags: ["**"] # Releases release: types: [published] jobs: - publish-artifacts: + publish-snapshot-artifacts: name: Publish / Artifacts + if: github.ref_type == 'branch' # Snapshots uses: playframework/.github/.github/workflows/publish.yml@v3 + with: + gradle-build-root: gradle-twirl + cmd: | + sbt ci-release + cd gradle-twirl + ./gradlew --no-daemon publishToSonatype -x test -PsonatypeUsername="$SONATYPE_USERNAME" -PsonatypePassword="$SONATYPE_PASSWORD" + secrets: inherit + + publish-release-artifacts: + name: Publish / Artifacts + if: github.ref_type == 'tag' # Releases + uses: playframework/.github/.github/workflows/publish.yml@v3 + with: + gradle-build-root: gradle-twirl + cmd: | + sbt ci-release + cd gradle-twirl + ./gradlew --no-daemon publishPlugins -x test -Pgradle.publish.key="$GRADLE_PUBLISH_KEY" -Pgradle.publish.secret="$GRADLE_PUBLISH_SECRET" secrets: inherit diff --git a/build.sbt b/build.sbt index 11620c3e..70b48b8a 100644 --- a/build.sbt +++ b/build.sbt @@ -43,7 +43,7 @@ lazy val twirl = project (Compile / headerSources) ++= ((baseDirectory.value ** ("*.properties" || "*.md" || "*.sbt" || "*.scala.html")) --- (baseDirectory.value ** "target" ** "*") - --- (baseDirectory.value ** "gradle-twirl" ** "*") // TODO: add headers to gradle plugin sources + --- (baseDirectory.value ** "gradle-twirl" ** "*") // Gradle Spotless plugin is used --- (baseDirectory.value / "docs" ** "*")).get ++ (baseDirectory.value / "project" ** "*.scala" --- (baseDirectory.value ** "target" ** "*")).get ) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index dfa84fd3..884e8149 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -2,12 +2,21 @@ * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. */ +import java.time.Duration +import java.util.Base64 +import kotlin.text.Charsets.UTF_8 + plugins { - // Apply the Java Gradle plugin development plugin to add support for developing Gradle plugins - `java-gradle-plugin` + id("com.gradle.plugin-publish") version "1.2.0" + id("io.github.gradle-nexus.publish-plugin") version "1.3.0" id("com.diffplug.spotless") version "6.19.0" + signing } +// group = "com.playframework" // group and plugin id must use same top level namespace +group = "com.typesafe.play" // TODO: uncomment line above and remove this +version = "0.0.1-SNAPSHOT" + repositories { // Use Maven Central for resolving dependencies. mavenCentral() @@ -38,10 +47,29 @@ testing { } } +signing { + val signingKey = Base64.getDecoder().decode(System.getenv("PGP_SECRET").orEmpty()).toString(UTF_8) + val signingPassword = System.getenv("PGP_PASSPHRASE").orEmpty() + useInMemoryPgpKeys(signingKey, signingPassword) +} + +nexusPublishing { + packageGroup.set(project.group.toString()) + clientTimeout.set(Duration.ofMinutes(60)) + this.repositories { + sonatype() + } +} + gradlePlugin { + website.set("https://www.playframework.com/documentation/latest/ScalaTemplates") + vcsUrl.set("https://github.com/playframework/twirl") // Define the plugin val greeting by plugins.creating { - id = "com.playframework.twirl" + id = "com.typesafe.play.twirl" // TODO: rename to "com.playframework.twirl" + displayName = "Twirl Plugin" + description = "A Gradle plugin to compile Twirl templates" + tags.set(listOf("playframework", "web", "template", "java", "scala")) implementationClass = "play.twirl.gradle.TwirlPlugin" } } diff --git a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties index 37aef8d3..221e3a60 100644 --- a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index f4e55d1c..9f1632da 100644 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -29,7 +29,7 @@ private File getSettingsFile() { @Test void canRunTask() throws IOException { writeString(getSettingsFile(), ""); - writeString(getBuildFile(), "plugins {" + " id('com.playframework.twirl')" + "}"); + writeString(getBuildFile(), "plugins {" + " id('com.typesafe.play.twirl')" + "}"); // Run the build GradleRunner runner = GradleRunner.create(); diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index aea245ee..96c15513 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -15,7 +15,7 @@ class TwirlPluginTest { void pluginRegistersATask() { // Create a test project and apply the plugin Project project = ProjectBuilder.builder().build(); - project.getPlugins().apply("com.playframework.twirl"); + project.getPlugins().apply("com.typesafe.play.twirl"); // Verify the result assertNotNull(project.getTasks().findByName("greeting")); diff --git a/scripts/test-code.sh b/scripts/test-code.sh index 1d396947..78c989cb 100755 --- a/scripts/test-code.sh +++ b/scripts/test-code.sh @@ -4,3 +4,4 @@ sbt "++$MATRIX_SCALA test; mavenPlugin/scripted" || exit 1 sbt +publishLocal plugin/test plugin/scripted || exit 1 +(cd gradle-twirl && ./gradlew clean check -x spotlessCheck --no-daemon) || exit 1 From a48e6b0d7e692951d3af049f2457a27aa1279499 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 23 Jun 2023 16:44:43 +0300 Subject: [PATCH 08/35] Sync version between Sbt and Gradle --- .github/workflows/build-test.yml | 1 + .gitignore | 2 ++ build.sbt | 19 ++++++++++++++++++- gradle-twirl/build.gradle.kts | 12 +++++++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 494d7751..ad3b538e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -27,6 +27,7 @@ jobs: gradle-build-root: gradle-twirl cmd: | sbt validateCode + sbt compiler/publishM2 cd gradle-twirl && ./gradlew clean spotlessCheck --no-daemon check-binary-compatibility: diff --git a/.gitignore b/.gitignore index 9e31eead..7a23fd8c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ project/plugins/project/ .idea .bloop/ + +compiler/version.properties diff --git a/build.sbt b/build.sbt index 70b48b8a..15b028bb 100644 --- a/build.sbt +++ b/build.sbt @@ -4,6 +4,8 @@ import Dependencies._ import sbtcrossproject.CrossPlugin.autoImport.crossProject import org.scalajs.jsenv.nodejs.NodeJSEnv +import java.util.Properties +import java.io.StringWriter val ScalaTestVersion = "3.2.17" @@ -43,6 +45,7 @@ lazy val twirl = project (Compile / headerSources) ++= ((baseDirectory.value ** ("*.properties" || "*.md" || "*.sbt" || "*.scala.html")) --- (baseDirectory.value ** "target" ** "*") + --- (baseDirectory.value / "compiler" / "version.properties") --- (baseDirectory.value ** "gradle-twirl" ** "*") // Gradle Spotless plugin is used --- (baseDirectory.value / "docs" ** "*")).get ++ (baseDirectory.value / "project" ** "*.scala" --- (baseDirectory.value ** "target" ** "*")).get @@ -117,7 +120,10 @@ lazy val compiler = project libraryDependencies += ("org.scalameta" %% "parsers" % "4.8.12").cross(CrossVersion.for3Use2_13), run / fork := true, buildInfoKeys := Seq[BuildInfoKey](scalaVersion), - buildInfoPackage := "play.twirl.compiler" + buildInfoPackage := "play.twirl.compiler", + publishM2 := publishM2.dependsOn(saveCompilerVersion).value, + publish := publish.dependsOn(saveCompilerVersion).value, + publishLocal := publishLocal.dependsOn(saveCompilerVersion).value ) .aggregate(parser) .dependsOn(apiJvm % Test, parser % "compile->compile;test->test") @@ -187,6 +193,17 @@ def generateVersionFile = Seq(file) } +def saveCompilerVersion = + Def.task { + val props = new Properties() + val writer = new StringWriter() + val file = baseDirectory.value / "version.properties" + props.setProperty("twirl.compiler.version", version.value) + props.store(writer, "") + IO.write(file, writer.getBuffer.toString) + Seq(file) + } + addCommandAlias( "validateCode", List( diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 884e8149..0d3f641b 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -4,6 +4,7 @@ import java.time.Duration import java.util.Base64 +import java.util.Properties import kotlin.text.Charsets.UTF_8 plugins { @@ -13,13 +14,22 @@ plugins { signing } +val compilerVersion: String = + Properties().apply { + val file = file("$projectDir/../compiler/version.properties") + if (!file.exists()) throw GradleException("Install Twirl Compiler to local Maven repository by `sbt compiler/publishM2` command") + file.inputStream().use { load(it) } + if (this.getProperty("twirl.compiler.version").isNullOrEmpty()) throw GradleException("`twirl.compiler.version` key didn't find in ${file.absolutePath}") + }.getProperty("twirl.compiler.version") + // group = "com.playframework" // group and plugin id must use same top level namespace group = "com.typesafe.play" // TODO: uncomment line above and remove this -version = "0.0.1-SNAPSHOT" +version = compilerVersion repositories { // Use Maven Central for resolving dependencies. mavenCentral() + mavenLocal() } testing { From ff2e1c6cfc51b71962f4870035bf7110350116d4 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 23 Aug 2023 16:59:12 +0300 Subject: [PATCH 09/35] Create SourceDirectorySet for Twirl --- gradle-twirl/build.gradle.kts | 2 +- .../gradle/TwirlPluginFunctionalTest.java | 11 +-- .../java/play/twirl/gradle/TwirlCompile.java | 27 ++++++ .../java/play/twirl/gradle/TwirlPlugin.java | 87 +++++++++++++++++-- .../twirl/gradle/TwirlSourceDirectorySet.java | 12 +++ .../DefaultTwirlSourceDirectorySet.java | 19 ++++ .../play/twirl/gradle/TwirlPluginTest.java | 4 +- 7 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 0d3f641b..5132ca5e 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -75,7 +75,7 @@ gradlePlugin { website.set("https://www.playframework.com/documentation/latest/ScalaTemplates") vcsUrl.set("https://github.com/playframework/twirl") // Define the plugin - val greeting by plugins.creating { + val twirl by plugins.creating { id = "com.typesafe.play.twirl" // TODO: rename to "com.playframework.twirl" displayName = "Twirl Plugin" description = "A Gradle plugin to compile Twirl templates" diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index 9f1632da..7e584d6c 100644 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -3,8 +3,6 @@ */ package play.twirl.gradle; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -29,18 +27,17 @@ private File getSettingsFile() { @Test void canRunTask() throws IOException { writeString(getSettingsFile(), ""); - writeString(getBuildFile(), "plugins {" + " id('com.typesafe.play.twirl')" + "}"); + writeString( + getBuildFile(), + "plugins {" + "id 'application'\n" + " id('com.typesafe.play.twirl')" + "}"); // Run the build GradleRunner runner = GradleRunner.create(); runner.forwardOutput(); runner.withPluginClasspath(); - runner.withArguments("greeting"); + runner.withArguments("compileTwirl"); runner.withProjectDir(projectDir); BuildResult result = runner.build(); - - // Verify the result - assertTrue(result.getOutput().contains("Hello from plugin 'com.playframework.twirl'")); } private void writeString(File file, String string) throws IOException { diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java new file mode 100644 index 00000000..c44079d1 --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -0,0 +1,27 @@ +/* +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ +package play.twirl.gradle; + +import java.io.File; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.SourceTask; +import org.gradle.api.tasks.TaskAction; + +public abstract class TwirlCompile extends SourceTask { + + @OutputDirectory + public abstract DirectoryProperty getDestinationDirectory(); + + @TaskAction + void compile() { + for (File sourceFile : getSource().getFiles()) { + try { + System.out.println("Compile Twirl template " + sourceFile.getName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 13809be0..0629fc88 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -3,19 +3,90 @@ */ package play.twirl.gradle; +import javax.inject.Inject; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.internal.lambdas.SerializableLambdas; +import org.gradle.api.internal.tasks.DefaultSourceSet; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.plugins.scala.ScalaBasePlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; +import play.twirl.gradle.internal.DefaultTwirlSourceDirectorySet; /** A simple 'hello world' plugin. */ public class TwirlPlugin implements Plugin { - public void apply(Project project) { - // Register a task - project - .getTasks() - .register( - "greeting", - task -> { - task.doLast(s -> System.out.println("Hello from plugin 'com.playframework.twirl'")); + + private final ObjectFactory objectFactory; + + @Inject + public TwirlPlugin(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + @Override + public void apply(final Project project) { + project.getPluginManager().apply(ScalaBasePlugin.class); + + configureSourceSetDefaults(project); + } + + private void configureSourceSetDefaults(final Project project) { + javaPluginExtension(project) + .getSourceSets() + .all( + (sourceSet) -> { + TwirlSourceDirectorySet twirlSource = getTwirlSourceDirectorySet(sourceSet); + sourceSet.getExtensions().add(TwirlSourceDirectorySet.class, "twirl", twirlSource); + twirlSource.srcDir(project.file("src/" + sourceSet.getName() + "/twirl")); + sourceSet + .getResources() + .getFilter() + .exclude( + SerializableLambdas.spec( + (element) -> twirlSource.contains(element.getFile()))); + sourceSet.getAllJava().source(twirlSource); + sourceSet.getAllSource().source(twirlSource); + createTwirlCompileTask(project, sourceSet, twirlSource); }); } + + private void createTwirlCompileTask( + final Project project, final SourceSet sourceSet, TwirlSourceDirectorySet twirlSource) { + final TaskProvider twirlTask = + project + .getTasks() + .register( + sourceSet.getCompileTaskName("twirl"), + TwirlCompile.class, + twirlCompile -> { + twirlCompile.setDescription("Compiles the " + twirlSource + "."); + twirlCompile.setSource(twirlSource); + twirlCompile + .getDestinationDirectory() + .convention(project.getLayout().getBuildDirectory()) + .dir( + "generated/sources/" + twirlSource.getName() + "/" + sourceSet.getName()); + }); + } + + private TwirlSourceDirectorySet getTwirlSourceDirectorySet(SourceSet sourceSet) { + String displayName = ((DefaultSourceSet) sourceSet).getDisplayName(); + TwirlSourceDirectorySet twirlSourceDirectorySet = + objectFactory.newInstance( + DefaultTwirlSourceDirectorySet.class, + objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); + twirlSourceDirectorySet.getFilter().include("**/*.scala.*"); + return twirlSourceDirectorySet; + } + + private static JavaPluginExtension javaPluginExtension(Project project) { + return extensionOf(project, JavaPluginExtension.class); + } + + private static T extensionOf(ExtensionAware extensionAware, Class type) { + return extensionAware.getExtensions().getByType(type); + } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java new file mode 100644 index 00000000..cc24814b --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -0,0 +1,12 @@ +/* +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ +package play.twirl.gradle; + +import org.gradle.api.file.SourceDirectorySet; + +/** + * A {@code TwirlSourceDirectorySet} defines the properties and methods added to a {@link + * org.gradle.api.tasks.SourceSet} by the {@code TwirlPlugin}. + */ +public interface TwirlSourceDirectorySet extends SourceDirectorySet {} diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java new file mode 100644 index 00000000..37851b6b --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -0,0 +1,19 @@ +/* +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ +package play.twirl.gradle.internal; + +import javax.inject.Inject; +import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.internal.file.DefaultSourceDirectorySet; +import org.gradle.api.internal.tasks.TaskDependencyFactory; +import play.twirl.gradle.TwirlSourceDirectorySet; + +public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet + implements TwirlSourceDirectorySet { + @Inject + public DefaultTwirlSourceDirectorySet( + SourceDirectorySet sourceDirectorySet, TaskDependencyFactory taskDependencyFactory) { + super(sourceDirectorySet, taskDependencyFactory); // Gradle 8+ + } +} diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index 96c15513..e5e0a061 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -15,9 +15,11 @@ class TwirlPluginTest { void pluginRegistersATask() { // Create a test project and apply the plugin Project project = ProjectBuilder.builder().build(); + project.getPlugins().apply("application"); project.getPlugins().apply("com.typesafe.play.twirl"); // Verify the result - assertNotNull(project.getTasks().findByName("greeting")); + assertNotNull(project.getTasks().findByName("compileTwirl")); + assertNotNull(project.getTasks().findByName("compileTestTwirl")); } } From 05ace95772702f12355cde456b2eae0415c3ad70 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 25 Aug 2023 13:05:52 +0300 Subject: [PATCH 10/35] Use Worker API for Gradle Plugin --- .../java/play/twirl/gradle/TwirlCompile.java | 18 +++++++++++++----- .../play/twirl/gradle/TwirlCompileAction.java | 19 +++++++++++++++++++ .../play/twirl/gradle/TwirlCompileParams.java | 12 ++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index c44079d1..3d1f29dd 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -4,24 +4,32 @@ package play.twirl.gradle; import java.io.File; +import javax.inject.Inject; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; +import org.gradle.workers.WorkQueue; +import org.gradle.workers.WorkerExecutor; public abstract class TwirlCompile extends SourceTask { @OutputDirectory public abstract DirectoryProperty getDestinationDirectory(); + @Inject + public abstract WorkerExecutor getWorkerExecutor(); + @TaskAction void compile() { + WorkQueue workQueue = getWorkerExecutor().noIsolation(); + for (File sourceFile : getSource().getFiles()) { - try { - System.out.println("Compile Twirl template " + sourceFile.getName()); - } catch (Exception e) { - throw new RuntimeException(e); - } + workQueue.submit( + TwirlCompileAction.class, + parameters -> { + parameters.getSourceFile().set(sourceFile); + }); } } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java new file mode 100644 index 00000000..40094f14 --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java @@ -0,0 +1,19 @@ +/* +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ +package play.twirl.gradle; + +import org.gradle.workers.WorkAction; + +public abstract class TwirlCompileAction implements WorkAction { + + @Override + public void execute() { + try { + System.out.println( + "Compile Twirl template " + getParameters().getSourceFile().getAsFile().get().getName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java new file mode 100644 index 00000000..2a7495cf --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java @@ -0,0 +1,12 @@ +/* +* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. +*/ +package play.twirl.gradle; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.workers.WorkParameters; + +public interface TwirlCompileParams extends WorkParameters { + + RegularFileProperty getSourceFile(); +} From 6d69b6d4f23b4d361216b40130c6ebcaafacffc4 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Mon, 28 Aug 2023 16:15:34 +0300 Subject: [PATCH 11/35] Create custom Twirl configuration for Gradle Plugin --- .github/workflows/build-test.yml | 2 +- gradle-twirl/build.gradle.kts | 12 +++++++- .../java/play/twirl/gradle/TwirlPlugin.java | 30 +++++++++++++++++-- scripts/test-code.sh | 2 +- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ad3b538e..693899b7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -27,7 +27,7 @@ jobs: gradle-build-root: gradle-twirl cmd: | sbt validateCode - sbt compiler/publishM2 + sbt +compiler/publishM2 cd gradle-twirl && ./gradlew clean spotlessCheck --no-daemon check-binary-compatibility: diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 5132ca5e..0ded2999 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -17,7 +17,7 @@ plugins { val compilerVersion: String = Properties().apply { val file = file("$projectDir/../compiler/version.properties") - if (!file.exists()) throw GradleException("Install Twirl Compiler to local Maven repository by `sbt compiler/publishM2` command") + if (!file.exists()) throw GradleException("Install Twirl Compiler to local Maven repository by `sbt +compiler/publishM2` command") file.inputStream().use { load(it) } if (this.getProperty("twirl.compiler.version").isNullOrEmpty()) throw GradleException("`twirl.compiler.version` key didn't find in ${file.absolutePath}") }.getProperty("twirl.compiler.version") @@ -32,6 +32,16 @@ repositories { mavenLocal() } +dependencies { + compileOnly("com.typesafe.play:twirl-compiler_2.13:$compilerVersion") +} + +tasks.jar { + manifest { + attributes("Implementation-Version" to version) + } +} + testing { suites { // Configure the built-in test suite diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 0629fc88..9ccee52d 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -6,6 +6,8 @@ import javax.inject.Inject; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.lambdas.SerializableLambdas; import org.gradle.api.internal.tasks.DefaultSourceSet; import org.gradle.api.model.ObjectFactory; @@ -30,9 +32,27 @@ public TwirlPlugin(ObjectFactory objectFactory) { public void apply(final Project project) { project.getPluginManager().apply(ScalaBasePlugin.class); + Configuration twirlConfiguration = createDefaultTwirlConfiguration(project); + configureSourceSetDefaults(project); } + private Configuration createDefaultTwirlConfiguration(Project project) { + // Get Twirl version from Gradle Plugin MANIFEST.MF + String twirlVersion = getClass().getPackage().getImplementationVersion(); + Configuration conf = project.getConfigurations().create("twirl"); + conf.setDescription("The Twirl compiler library."); + conf.setVisible(false); + conf.setTransitive(true); + conf.defaultDependencies( + dependencies -> + dependencies.add( + project + .getDependencies() + .create("com.typesafe.play:twirl-compiler_2.13:" + twirlVersion))); + return conf; + } + private void configureSourceSetDefaults(final Project project) { javaPluginExtension(project) .getSourceSets() @@ -64,11 +84,15 @@ private void createTwirlCompileTask( twirlCompile -> { twirlCompile.setDescription("Compiles the " + twirlSource + "."); twirlCompile.setSource(twirlSource); + DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); twirlCompile .getDestinationDirectory() - .convention(project.getLayout().getBuildDirectory()) - .dir( - "generated/sources/" + twirlSource.getName() + "/" + sourceSet.getName()); + .convention( + buildDirectory.dir( + "generated/sources/" + + twirlSource.getName() + + "/" + + sourceSet.getName())); }); } diff --git a/scripts/test-code.sh b/scripts/test-code.sh index 78c989cb..0a35fa41 100755 --- a/scripts/test-code.sh +++ b/scripts/test-code.sh @@ -3,5 +3,5 @@ # Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. sbt "++$MATRIX_SCALA test; mavenPlugin/scripted" || exit 1 -sbt +publishLocal plugin/test plugin/scripted || exit 1 +sbt +publishLocal +compiler/publishM2 plugin/test plugin/scripted || exit 1 (cd gradle-twirl && ./gradlew clean check -x spotlessCheck --no-daemon) || exit 1 From cb46b7be277030d647c357d0bcd2e7bb60b2c17f Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Mon, 28 Aug 2023 16:56:46 +0300 Subject: [PATCH 12/35] Fix license header in Gradle Plugin sources --- gradle-twirl/build.gradle.kts | 7 +++---- gradle-twirl/settings.gradle.kts | 4 ++-- .../java/play/twirl/gradle/TwirlPluginFunctionalTest.java | 4 ++-- .../src/main/java/play/twirl/gradle/TwirlCompile.java | 4 ++-- .../main/java/play/twirl/gradle/TwirlCompileAction.java | 4 ++-- .../main/java/play/twirl/gradle/TwirlCompileParams.java | 4 ++-- .../src/main/java/play/twirl/gradle/TwirlPlugin.java | 4 ++-- .../java/play/twirl/gradle/TwirlSourceDirectorySet.java | 4 ++-- .../gradle/internal/DefaultTwirlSourceDirectorySet.java | 4 ++-- .../src/test/java/play/twirl/gradle/TwirlPluginTest.java | 4 ++-- 10 files changed, 21 insertions(+), 22 deletions(-) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 0ded2999..7bb9cd77 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -1,7 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ - + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ import java.time.Duration import java.util.Base64 import java.util.Properties @@ -98,7 +97,7 @@ gradlePlugin.testSourceSets.add(sourceSets["functionalTest"]) val headerLicense = "Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " val headerLicenseHash = "# $headerLicense" -val headerLicenseJava = "/*\n* $headerLicense\n*/" +val headerLicenseJava = "/*\n * $headerLicense\n */" spotless { java { diff --git a/gradle-twirl/settings.gradle.kts b/gradle-twirl/settings.gradle.kts index b8b5e124..6cf58d57 100644 --- a/gradle-twirl/settings.gradle.kts +++ b/gradle-twirl/settings.gradle.kts @@ -1,5 +1,5 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ rootProject.name = "gradle-twirl" diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index 7e584d6c..45a57128 100644 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import java.io.File; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index 3d1f29dd..b94ed8f9 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import java.io.File; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java index 40094f14..f259f38b 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import org.gradle.workers.WorkAction; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java index 2a7495cf..70df208c 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileParams.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import org.gradle.api.file.RegularFileProperty; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 9ccee52d..79fca0cb 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import javax.inject.Inject; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java index cc24814b..83944bcf 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import org.gradle.api.file.SourceDirectorySet; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index 37851b6b..a7b06329 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle.internal; import javax.inject.Inject; diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index e5e0a061..e89e2ed8 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -1,6 +1,6 @@ /* -* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. -*/ + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ package play.twirl.gradle; import static org.junit.jupiter.api.Assertions.assertNotNull; From a381ec5ddebb9d98bb364a4ade0dadaa37eec0d2 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Mon, 28 Aug 2023 17:27:53 +0300 Subject: [PATCH 13/35] [Gradle] Add destination directory to the action params --- .github/workflows/publish.yml | 4 ++-- .../src/main/java/play/twirl/gradle/TwirlCompile.java | 1 + .../src/main/java/play/twirl/gradle/TwirlCompileAction.java | 6 +++++- .../twirl/gradle/{ => internal}/TwirlCompileParams.java | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) rename gradle-twirl/src/main/java/play/twirl/gradle/{ => internal}/TwirlCompileParams.java (72%) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ffeb952e..9f4d846a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: with: gradle-build-root: gradle-twirl cmd: | - sbt ci-release + sbt +compiler/publishM2 ci-release cd gradle-twirl ./gradlew --no-daemon publishToSonatype -x test -PsonatypeUsername="$SONATYPE_USERNAME" -PsonatypePassword="$SONATYPE_PASSWORD" secrets: inherit @@ -29,7 +29,7 @@ jobs: with: gradle-build-root: gradle-twirl cmd: | - sbt ci-release + sbt +compiler/publishM2 ci-release cd gradle-twirl ./gradlew --no-daemon publishPlugins -x test -Pgradle.publish.key="$GRADLE_PUBLISH_KEY" -Pgradle.publish.secret="$GRADLE_PUBLISH_SECRET" secrets: inherit diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index b94ed8f9..e4ad9db9 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -29,6 +29,7 @@ void compile() { TwirlCompileAction.class, parameters -> { parameters.getSourceFile().set(sourceFile); + parameters.getDestinationDirectory().set(getDestinationDirectory()); }); } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java index f259f38b..48afb1dd 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java @@ -4,6 +4,7 @@ package play.twirl.gradle; import org.gradle.workers.WorkAction; +import play.twirl.gradle.internal.TwirlCompileParams; public abstract class TwirlCompileAction implements WorkAction { @@ -11,7 +12,10 @@ public abstract class TwirlCompileAction implements WorkAction, 2011-2021 Lightbend Inc. */ -package play.twirl.gradle; +package play.twirl.gradle.internal; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; import org.gradle.workers.WorkParameters; public interface TwirlCompileParams extends WorkParameters { RegularFileProperty getSourceFile(); + + DirectoryProperty getDestinationDirectory(); } From 2d2ef65270b8091eb89ed2db19b9608304a3fd02 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Mon, 28 Aug 2023 18:01:08 +0300 Subject: [PATCH 14/35] [Gradle] Use isolation classloader for Twirl compilation --- .../main/java/play/twirl/gradle/TwirlCompile.java | 13 ++++++++++++- .../main/java/play/twirl/gradle/TwirlPlugin.java | 13 +++++++++---- .../gradle/{ => internal}/TwirlCompileAction.java | 3 +-- 3 files changed, 22 insertions(+), 7 deletions(-) rename gradle-twirl/src/main/java/play/twirl/gradle/{ => internal}/TwirlCompileAction.java (89%) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index e4ad9db9..f6ea913d 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -5,15 +5,21 @@ import java.io.File; import javax.inject.Inject; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; import org.gradle.workers.WorkQueue; import org.gradle.workers.WorkerExecutor; +import play.twirl.gradle.internal.TwirlCompileAction; public abstract class TwirlCompile extends SourceTask { + @InputFiles + public abstract ConfigurableFileCollection getTwirlClasspath(); + @OutputDirectory public abstract DirectoryProperty getDestinationDirectory(); @@ -22,7 +28,12 @@ public abstract class TwirlCompile extends SourceTask { @TaskAction void compile() { - WorkQueue workQueue = getWorkerExecutor().noIsolation(); + WorkQueue workQueue = + getWorkerExecutor() + .classLoaderIsolation( + workerSpec -> { + workerSpec.getClasspath().from(getTwirlClasspath()); + }); for (File sourceFile : getSource().getFiles()) { workQueue.submit( diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 79fca0cb..50d62981 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -34,7 +34,7 @@ public void apply(final Project project) { Configuration twirlConfiguration = createDefaultTwirlConfiguration(project); - configureSourceSetDefaults(project); + configureSourceSetDefaults(project, twirlConfiguration); } private Configuration createDefaultTwirlConfiguration(Project project) { @@ -53,7 +53,8 @@ private Configuration createDefaultTwirlConfiguration(Project project) { return conf; } - private void configureSourceSetDefaults(final Project project) { + private void configureSourceSetDefaults( + final Project project, final Configuration twirlConfiguration) { javaPluginExtension(project) .getSourceSets() .all( @@ -69,12 +70,15 @@ private void configureSourceSetDefaults(final Project project) { (element) -> twirlSource.contains(element.getFile()))); sourceSet.getAllJava().source(twirlSource); sourceSet.getAllSource().source(twirlSource); - createTwirlCompileTask(project, sourceSet, twirlSource); + createTwirlCompileTask(project, sourceSet, twirlSource, twirlConfiguration); }); } private void createTwirlCompileTask( - final Project project, final SourceSet sourceSet, TwirlSourceDirectorySet twirlSource) { + final Project project, + final SourceSet sourceSet, + TwirlSourceDirectorySet twirlSource, + final Configuration twirlConfiguration) { final TaskProvider twirlTask = project .getTasks() @@ -83,6 +87,7 @@ private void createTwirlCompileTask( TwirlCompile.class, twirlCompile -> { twirlCompile.setDescription("Compiles the " + twirlSource + "."); + twirlCompile.getTwirlClasspath().setFrom(twirlConfiguration); twirlCompile.setSource(twirlSource); DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); twirlCompile diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java similarity index 89% rename from gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java rename to gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java index 48afb1dd..00cafb07 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompileAction.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java @@ -1,10 +1,9 @@ /* * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. */ -package play.twirl.gradle; +package play.twirl.gradle.internal; import org.gradle.workers.WorkAction; -import play.twirl.gradle.internal.TwirlCompileParams; public abstract class TwirlCompileAction implements WorkAction { From 46ca85d0a912f6428b201601b421167cb6c1c95e Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Tue, 29 Aug 2023 14:26:05 +0300 Subject: [PATCH 15/35] [Gradle] Add source directory to the action params --- .../java/play/twirl/gradle/TwirlCompile.java | 26 ++++++++++++++----- .../gradle/internal/TwirlCompileParams.java | 2 ++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index f6ea913d..89eade8e 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -3,10 +3,12 @@ */ package play.twirl.gradle; -import java.io.File; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.internal.file.RelativeFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceTask; @@ -30,18 +32,28 @@ public abstract class TwirlCompile extends SourceTask { void compile() { WorkQueue workQueue = getWorkerExecutor() - .classLoaderIsolation( - workerSpec -> { - workerSpec.getClasspath().from(getTwirlClasspath()); - }); + .classLoaderIsolation(spec -> spec.getClasspath().from(getTwirlClasspath())); - for (File sourceFile : getSource().getFiles()) { + for (RelativeFile sourceFile : getSourceAsRelativeFiles()) { workQueue.submit( TwirlCompileAction.class, parameters -> { - parameters.getSourceFile().set(sourceFile); + parameters.getSourceFile().set(sourceFile.getFile()); + parameters.getSourceDirectory().set(sourceFile.getBaseDir()); parameters.getDestinationDirectory().set(getDestinationDirectory()); }); } } + + private Iterable getSourceAsRelativeFiles() { + List relativeFiles = new ArrayList<>(); + getSource() + .visit( + fvd -> { + if (fvd.getFile().isFile()) { + relativeFiles.add(new RelativeFile(fvd.getFile(), fvd.getRelativePath())); + } + }); + return relativeFiles; + } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index 5da91746..4d8d8fcf 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -11,5 +11,7 @@ public interface TwirlCompileParams extends WorkParameters { RegularFileProperty getSourceFile(); + DirectoryProperty getSourceDirectory(); + DirectoryProperty getDestinationDirectory(); } From 14f228d6bf1a7879a119cd5d8eb26c6e3b470e72 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Tue, 29 Aug 2023 16:48:19 +0300 Subject: [PATCH 16/35] [Gradle] Add formatter type to the action params --- .../java/play/twirl/gradle/TwirlCompile.java | 25 +++++++++++++++++++ .../java/play/twirl/gradle/TwirlPlugin.java | 14 +++++++++++ .../twirl/gradle/TwirlSourceDirectorySet.java | 6 ++++- .../DefaultTwirlSourceDirectorySet.java | 15 ++++++++++- .../gradle/internal/TwirlCompileParams.java | 3 +++ 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index 89eade8e..b88c3b67 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -3,16 +3,22 @@ */ package play.twirl.gradle; +import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; import javax.inject.Inject; +import org.gradle.api.GradleException; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.file.RelativeFile; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.FileUtils; import org.gradle.workers.WorkQueue; import org.gradle.workers.WorkerExecutor; import play.twirl.gradle.internal.TwirlCompileAction; @@ -25,6 +31,9 @@ public abstract class TwirlCompile extends SourceTask { @OutputDirectory public abstract DirectoryProperty getDestinationDirectory(); + @Input + public abstract MapProperty getTemplateFormats(); + @Inject public abstract WorkerExecutor getWorkerExecutor(); @@ -34,6 +43,7 @@ void compile() { getWorkerExecutor() .classLoaderIsolation(spec -> spec.getClasspath().from(getTwirlClasspath())); + Map templateFormats = getTemplateFormats().get(); for (RelativeFile sourceFile : getSourceAsRelativeFiles()) { workQueue.submit( TwirlCompileAction.class, @@ -41,10 +51,25 @@ void compile() { parameters.getSourceFile().set(sourceFile.getFile()); parameters.getSourceDirectory().set(sourceFile.getBaseDir()); parameters.getDestinationDirectory().set(getDestinationDirectory()); + parameters + .getFormatterType() + .set(getFormatterType(templateFormats, sourceFile.getFile())); }); } } + private String getFormatterType(Map formats, File file) { + return formats.keySet().stream() + .filter(ext -> FileUtils.hasExtensionIgnoresCase(file.getName(), ext)) + .findFirst() + .orElseThrow( + () -> + new GradleException( + String.format( + "Unknown template format of '%s'. Possible extentions: [%s]", + file.getName(), String.join(", ", formats.keySet())))); + } + private Iterable getSourceAsRelativeFiles() { List relativeFiles = new ArrayList<>(); getSource() diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 50d62981..415290e8 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -3,6 +3,7 @@ */ package play.twirl.gradle; +import java.util.Map; import javax.inject.Inject; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -21,6 +22,17 @@ /** A simple 'hello world' plugin. */ public class TwirlPlugin implements Plugin { + private static final Map DEFAULT_TEMPLATE_FORMATS = + Map.of( + "html", + "play.twirl.api.HtmlFormat", + "txt", + "play.twirl.api.TxtFormat", + "xml", + "play.twirl.api.XmlFormat", + "js", + "play.twirl.api.JavaScriptFormat"); + private final ObjectFactory objectFactory; @Inject @@ -89,6 +101,7 @@ private void createTwirlCompileTask( twirlCompile.setDescription("Compiles the " + twirlSource + "."); twirlCompile.getTwirlClasspath().setFrom(twirlConfiguration); twirlCompile.setSource(twirlSource); + twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); twirlCompile .getDestinationDirectory() @@ -108,6 +121,7 @@ private TwirlSourceDirectorySet getTwirlSourceDirectorySet(SourceSet sourceSet) DefaultTwirlSourceDirectorySet.class, objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); twirlSourceDirectorySet.getFilter().include("**/*.scala.*"); + twirlSourceDirectorySet.getTemplateFormats().convention(DEFAULT_TEMPLATE_FORMATS); return twirlSourceDirectorySet; } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java index 83944bcf..7e829a79 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -4,9 +4,13 @@ package play.twirl.gradle; import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.provider.MapProperty; /** * A {@code TwirlSourceDirectorySet} defines the properties and methods added to a {@link * org.gradle.api.tasks.SourceSet} by the {@code TwirlPlugin}. */ -public interface TwirlSourceDirectorySet extends SourceDirectorySet {} +public interface TwirlSourceDirectorySet extends SourceDirectorySet { + + MapProperty getTemplateFormats(); +} diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index a7b06329..00ea2f0f 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -7,13 +7,26 @@ import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.internal.file.DefaultSourceDirectorySet; import org.gradle.api.internal.tasks.TaskDependencyFactory; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.MapProperty; import play.twirl.gradle.TwirlSourceDirectorySet; public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet implements TwirlSourceDirectorySet { + + private final MapProperty templateFormats; + @Inject public DefaultTwirlSourceDirectorySet( - SourceDirectorySet sourceDirectorySet, TaskDependencyFactory taskDependencyFactory) { + SourceDirectorySet sourceDirectorySet, + TaskDependencyFactory taskDependencyFactory, + ObjectFactory objectFactory) { super(sourceDirectorySet, taskDependencyFactory); // Gradle 8+ + this.templateFormats = objectFactory.mapProperty(String.class, String.class); + } + + @Override + public MapProperty getTemplateFormats() { + return templateFormats; } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index 4d8d8fcf..25b6ad30 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -5,6 +5,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; import org.gradle.workers.WorkParameters; public interface TwirlCompileParams extends WorkParameters { @@ -14,4 +15,6 @@ public interface TwirlCompileParams extends WorkParameters { DirectoryProperty getSourceDirectory(); DirectoryProperty getDestinationDirectory(); + + Property getFormatterType(); } From 1ce34a5c6760ef13f437c470bbfb56ba71dd0884 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 30 Aug 2023 11:19:32 +0300 Subject: [PATCH 17/35] [Gradle] Add template imports to the action params --- .../java/play/twirl/gradle/TwirlCompile.java | 5 ++++ .../play/twirl/gradle/TwirlExtension.java | 11 +++++++ .../java/play/twirl/gradle/TwirlPlugin.java | 29 ++++++++++++++----- .../twirl/gradle/TwirlSourceDirectorySet.java | 3 ++ .../DefaultTwirlSourceDirectorySet.java | 9 ++++++ .../gradle/internal/TwirlCompileParams.java | 3 ++ 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index b88c3b67..00e5524f 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -13,6 +13,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.file.RelativeFile; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; @@ -34,6 +35,9 @@ public abstract class TwirlCompile extends SourceTask { @Input public abstract MapProperty getTemplateFormats(); + @Input + public abstract SetProperty getTemplateImports(); + @Inject public abstract WorkerExecutor getWorkerExecutor(); @@ -54,6 +58,7 @@ void compile() { parameters .getFormatterType() .set(getFormatterType(templateFormats, sourceFile.getFile())); + parameters.getTemplateImports().set(getTemplateImports()); }); } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java new file mode 100644 index 00000000..a4728193 --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ +package play.twirl.gradle; + +import org.gradle.api.provider.Property; + +public abstract class TwirlExtension { + + public abstract Property getScalaVersion(); +} diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 415290e8..215c3649 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -8,6 +8,7 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.lambdas.SerializableLambdas; import org.gradle.api.internal.tasks.DefaultSourceSet; @@ -22,6 +23,8 @@ /** A simple 'hello world' plugin. */ public class TwirlPlugin implements Plugin { + private static final String DEFAULT_SCALA_VERSION = "2.13"; + private static final Map DEFAULT_TEMPLATE_FORMATS = Map.of( "html", @@ -44,24 +47,33 @@ public TwirlPlugin(ObjectFactory objectFactory) { public void apply(final Project project) { project.getPluginManager().apply(ScalaBasePlugin.class); - Configuration twirlConfiguration = createDefaultTwirlConfiguration(project); + TwirlExtension twirlExtension = project.getExtensions().create("twirl", TwirlExtension.class); + twirlExtension.getScalaVersion().convention(DEFAULT_SCALA_VERSION); + + Configuration twirlConfiguration = createDefaultTwirlConfiguration(project, twirlExtension); configureSourceSetDefaults(project, twirlConfiguration); } - private Configuration createDefaultTwirlConfiguration(Project project) { + private Configuration createDefaultTwirlConfiguration( + Project project, TwirlExtension twirlExtension) { // Get Twirl version from Gradle Plugin MANIFEST.MF String twirlVersion = getClass().getPackage().getImplementationVersion(); Configuration conf = project.getConfigurations().create("twirl"); conf.setDescription("The Twirl compiler library."); conf.setVisible(false); conf.setTransitive(true); - conf.defaultDependencies( - dependencies -> - dependencies.add( - project - .getDependencies() - .create("com.typesafe.play:twirl-compiler_2.13:" + twirlVersion))); + project.afterEvaluate( + __ -> { + Dependency twirlCompiler = + project + .getDependencies() + .create( + String.format( + "com.typesafe.play:twirl-compiler_%s:%s", + twirlExtension.getScalaVersion().get(), twirlVersion)); + conf.defaultDependencies(dependencies -> dependencies.add(twirlCompiler)); + }); return conf; } @@ -102,6 +114,7 @@ private void createTwirlCompileTask( twirlCompile.getTwirlClasspath().setFrom(twirlConfiguration); twirlCompile.setSource(twirlSource); twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); + twirlCompile.getTemplateImports().convention(twirlSource.getTemplateImports()); DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); twirlCompile .getDestinationDirectory() diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java index 7e829a79..54bc9aee 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -5,6 +5,7 @@ import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.SetProperty; /** * A {@code TwirlSourceDirectorySet} defines the properties and methods added to a {@link @@ -13,4 +14,6 @@ public interface TwirlSourceDirectorySet extends SourceDirectorySet { MapProperty getTemplateFormats(); + + SetProperty getTemplateImports(); } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index 00ea2f0f..c6d90f85 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -9,6 +9,7 @@ import org.gradle.api.internal.tasks.TaskDependencyFactory; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.SetProperty; import play.twirl.gradle.TwirlSourceDirectorySet; public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet @@ -16,6 +17,8 @@ public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet private final MapProperty templateFormats; + private final SetProperty templateImports; + @Inject public DefaultTwirlSourceDirectorySet( SourceDirectorySet sourceDirectorySet, @@ -23,10 +26,16 @@ public DefaultTwirlSourceDirectorySet( ObjectFactory objectFactory) { super(sourceDirectorySet, taskDependencyFactory); // Gradle 8+ this.templateFormats = objectFactory.mapProperty(String.class, String.class); + this.templateImports = objectFactory.setProperty(String.class); } @Override public MapProperty getTemplateFormats() { return templateFormats; } + + @Override + public SetProperty getTemplateImports() { + return templateImports; + } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index 25b6ad30..68600b23 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -6,6 +6,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.workers.WorkParameters; public interface TwirlCompileParams extends WorkParameters { @@ -17,4 +18,6 @@ public interface TwirlCompileParams extends WorkParameters { DirectoryProperty getDestinationDirectory(); Property getFormatterType(); + + SetProperty getTemplateImports(); } From 435125902526c4abd5052af49f015b79e70d556a Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 30 Aug 2023 12:00:15 +0300 Subject: [PATCH 18/35] [Gradle] Add constructor annotations to the action params --- .../src/main/java/play/twirl/gradle/TwirlCompile.java | 5 +++++ .../src/main/java/play/twirl/gradle/TwirlPlugin.java | 3 +++ .../java/play/twirl/gradle/TwirlSourceDirectorySet.java | 3 +++ .../gradle/internal/DefaultTwirlSourceDirectorySet.java | 9 +++++++++ .../play/twirl/gradle/internal/TwirlCompileParams.java | 3 +++ 5 files changed, 23 insertions(+) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index 00e5524f..410ff6df 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -12,6 +12,7 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.file.RelativeFile; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; @@ -38,6 +39,9 @@ public abstract class TwirlCompile extends SourceTask { @Input public abstract SetProperty getTemplateImports(); + @Input + public abstract ListProperty getConstructorAnnotations(); + @Inject public abstract WorkerExecutor getWorkerExecutor(); @@ -59,6 +63,7 @@ void compile() { .getFormatterType() .set(getFormatterType(templateFormats, sourceFile.getFile())); parameters.getTemplateImports().set(getTemplateImports()); + parameters.getConstructorAnnotations().set(getConstructorAnnotations()); }); } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 215c3649..0683b056 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -115,6 +115,9 @@ private void createTwirlCompileTask( twirlCompile.setSource(twirlSource); twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); twirlCompile.getTemplateImports().convention(twirlSource.getTemplateImports()); + twirlCompile + .getConstructorAnnotations() + .convention(twirlSource.getConstructorAnnotations()); DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); twirlCompile .getDestinationDirectory() diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java index 54bc9aee..8446fceb 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -4,6 +4,7 @@ package play.twirl.gradle; import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.SetProperty; @@ -16,4 +17,6 @@ public interface TwirlSourceDirectorySet extends SourceDirectorySet { MapProperty getTemplateFormats(); SetProperty getTemplateImports(); + + ListProperty getConstructorAnnotations(); } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index c6d90f85..959c2a34 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -8,6 +8,7 @@ import org.gradle.api.internal.file.DefaultSourceDirectorySet; import org.gradle.api.internal.tasks.TaskDependencyFactory; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.SetProperty; import play.twirl.gradle.TwirlSourceDirectorySet; @@ -19,6 +20,8 @@ public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet private final SetProperty templateImports; + private final ListProperty constructorAnnotations; + @Inject public DefaultTwirlSourceDirectorySet( SourceDirectorySet sourceDirectorySet, @@ -27,6 +30,7 @@ public DefaultTwirlSourceDirectorySet( super(sourceDirectorySet, taskDependencyFactory); // Gradle 8+ this.templateFormats = objectFactory.mapProperty(String.class, String.class); this.templateImports = objectFactory.setProperty(String.class); + this.constructorAnnotations = objectFactory.listProperty(String.class); } @Override @@ -38,4 +42,9 @@ public MapProperty getTemplateFormats() { public SetProperty getTemplateImports() { return templateImports; } + + @Override + public ListProperty getConstructorAnnotations() { + return constructorAnnotations; + } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index 68600b23..60fb9a43 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -5,6 +5,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; import org.gradle.workers.WorkParameters; @@ -20,4 +21,6 @@ public interface TwirlCompileParams extends WorkParameters { Property getFormatterType(); SetProperty getTemplateImports(); + + ListProperty getConstructorAnnotations(); } From 848c3e1ea33eb1ffbfa5ec1c5020cb42b5e71e30 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 30 Aug 2023 12:52:55 +0300 Subject: [PATCH 19/35] [Gradle] Add source encoding to the action params --- .../src/main/java/play/twirl/gradle/TwirlCompile.java | 5 +++++ .../src/main/java/play/twirl/gradle/TwirlPlugin.java | 1 + .../play/twirl/gradle/TwirlSourceDirectorySet.java | 3 +++ .../internal/DefaultTwirlSourceDirectorySet.java | 11 +++++++++++ .../twirl/gradle/internal/TwirlCompileParams.java | 2 ++ 5 files changed, 22 insertions(+) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index 410ff6df..0a259dc2 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -14,6 +14,7 @@ import org.gradle.api.internal.file.RelativeFile; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; @@ -42,6 +43,9 @@ public abstract class TwirlCompile extends SourceTask { @Input public abstract ListProperty getConstructorAnnotations(); + @Input + public abstract Property getSourceEncoding(); + @Inject public abstract WorkerExecutor getWorkerExecutor(); @@ -64,6 +68,7 @@ void compile() { .set(getFormatterType(templateFormats, sourceFile.getFile())); parameters.getTemplateImports().set(getTemplateImports()); parameters.getConstructorAnnotations().set(getConstructorAnnotations()); + parameters.getSourceEncoding().set(getSourceEncoding()); }); } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 0683b056..a2adad79 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -115,6 +115,7 @@ private void createTwirlCompileTask( twirlCompile.setSource(twirlSource); twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); twirlCompile.getTemplateImports().convention(twirlSource.getTemplateImports()); + twirlCompile.getSourceEncoding().convention(twirlSource.getSourceEncoding()); twirlCompile .getConstructorAnnotations() .convention(twirlSource.getConstructorAnnotations()); diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java index 8446fceb..b767bd98 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -6,6 +6,7 @@ import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; /** @@ -19,4 +20,6 @@ public interface TwirlSourceDirectorySet extends SourceDirectorySet { SetProperty getTemplateImports(); ListProperty getConstructorAnnotations(); + + Property getSourceEncoding(); } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index 959c2a34..83c360b5 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -3,6 +3,8 @@ */ package play.twirl.gradle.internal; +import static java.nio.charset.StandardCharsets.UTF_8; + import javax.inject.Inject; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.internal.file.DefaultSourceDirectorySet; @@ -10,6 +12,7 @@ import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; import play.twirl.gradle.TwirlSourceDirectorySet; @@ -22,6 +25,8 @@ public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet private final ListProperty constructorAnnotations; + private final Property sourceEncoding; + @Inject public DefaultTwirlSourceDirectorySet( SourceDirectorySet sourceDirectorySet, @@ -31,6 +36,7 @@ public DefaultTwirlSourceDirectorySet( this.templateFormats = objectFactory.mapProperty(String.class, String.class); this.templateImports = objectFactory.setProperty(String.class); this.constructorAnnotations = objectFactory.listProperty(String.class); + this.sourceEncoding = objectFactory.property(String.class).convention(UTF_8.name()); } @Override @@ -47,4 +53,9 @@ public SetProperty getTemplateImports() { public ListProperty getConstructorAnnotations() { return constructorAnnotations; } + + @Override + public Property getSourceEncoding() { + return sourceEncoding; + } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index 60fb9a43..a3b1b973 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -23,4 +23,6 @@ public interface TwirlCompileParams extends WorkParameters { SetProperty getTemplateImports(); ListProperty getConstructorAnnotations(); + + Property getSourceEncoding(); } From 9f905b5712ff4b5c891c701cd567a2a04dd38b9e Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 30 Aug 2023 13:43:29 +0300 Subject: [PATCH 20/35] [Gradle] Use real Twirl compiler --- .../gradle/internal/TwirlCompileAction.java | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java index 00cafb07..7ba573ea 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java @@ -3,19 +3,50 @@ */ package play.twirl.gradle.internal; +import java.io.File; +import java.util.Collection; +import java.util.List; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; import org.gradle.workers.WorkAction; +import play.japi.twirl.compiler.TwirlCompiler; +import scala.io.Codec; public abstract class TwirlCompileAction implements WorkAction { + private static final Logger LOGGER = Logging.getLogger(TwirlCompileAction.class); + @Override public void execute() { try { - System.out.println( - "Compile Twirl template " - + getParameters().getSourceFile().getAsFile().get().getName() - + " into " - + getParameters().getDestinationDirectory().getAsFile().get().getCanonicalPath()); + File sourceFile = getParameters().getSourceFile().getAsFile().get(); + File sourceDirectory = getParameters().getSourceDirectory().getAsFile().get(); + File destinationDirectory = getParameters().getDestinationDirectory().getAsFile().get(); + String formatterType = getParameters().getFormatterType().get(); + getParameters().getTemplateImports().addAll(TwirlCompiler.DEFAULT_IMPORTS); + Collection imports = getParameters().getTemplateImports().get(); + List constructorAnnotations = getParameters().getConstructorAnnotations().get(); + String sourceEncoding = getParameters().getSourceEncoding().get(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Compile Twirl template [{}/{}] {} from {} into {}", + formatterType, + sourceEncoding, + sourceFile.getName(), + sourceDirectory.getCanonicalPath(), + destinationDirectory.getCanonicalPath()); + } + TwirlCompiler.compile( + sourceFile, + sourceDirectory, + destinationDirectory, + formatterType, + imports, + constructorAnnotations, + Codec.string2codec(sourceEncoding), + false); } catch (Exception e) { + LOGGER.error(e.getMessage(), e); throw new RuntimeException(e); } } From 413541173647aa7981b0757b1218d16844d5669c Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 30 Aug 2023 16:12:20 +0300 Subject: [PATCH 21/35] [Gradle] Add compiled templates to scala source set --- .../java/play/twirl/gradle/TwirlCompile.java | 1 + .../java/play/twirl/gradle/TwirlPlugin.java | 69 ++++++++++--------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index 0a259dc2..71eff915 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -77,6 +77,7 @@ private String getFormatterType(Map formats, File file) { return formats.keySet().stream() .filter(ext -> FileUtils.hasExtensionIgnoresCase(file.getName(), ext)) .findFirst() + .map(formats::get) .orElseThrow( () -> new GradleException( diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index a2adad79..d8ef2efd 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -16,6 +16,7 @@ import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.scala.ScalaBasePlugin; +import org.gradle.api.tasks.ScalaSourceDirectorySet; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import play.twirl.gradle.internal.DefaultTwirlSourceDirectorySet; @@ -55,10 +56,13 @@ public void apply(final Project project) { configureSourceSetDefaults(project, twirlConfiguration); } + /** Get Twirl version from Gradle Plugin MANIFEST.MF */ + private String getDefaultTwirlVersion() { + return getClass().getPackage().getImplementationVersion(); + } + private Configuration createDefaultTwirlConfiguration( Project project, TwirlExtension twirlExtension) { - // Get Twirl version from Gradle Plugin MANIFEST.MF - String twirlVersion = getClass().getPackage().getImplementationVersion(); Configuration conf = project.getConfigurations().create("twirl"); conf.setDescription("The Twirl compiler library."); conf.setVisible(false); @@ -71,7 +75,7 @@ private Configuration createDefaultTwirlConfiguration( .create( String.format( "com.typesafe.play:twirl-compiler_%s:%s", - twirlExtension.getScalaVersion().get(), twirlVersion)); + twirlExtension.getScalaVersion().get(), getDefaultTwirlVersion())); conf.defaultDependencies(dependencies -> dependencies.add(twirlCompiler)); }); return conf; @@ -94,41 +98,44 @@ private void configureSourceSetDefaults( (element) -> twirlSource.contains(element.getFile()))); sourceSet.getAllJava().source(twirlSource); sourceSet.getAllSource().source(twirlSource); - createTwirlCompileTask(project, sourceSet, twirlSource, twirlConfiguration); + + TaskProvider twirlTask = + createTwirlCompileTask(project, sourceSet, twirlSource, twirlConfiguration); + + extensionOf(sourceSet, ScalaSourceDirectorySet.class).srcDir(twirlTask); }); } - private void createTwirlCompileTask( + private TaskProvider createTwirlCompileTask( final Project project, final SourceSet sourceSet, TwirlSourceDirectorySet twirlSource, final Configuration twirlConfiguration) { - final TaskProvider twirlTask = - project - .getTasks() - .register( - sourceSet.getCompileTaskName("twirl"), - TwirlCompile.class, - twirlCompile -> { - twirlCompile.setDescription("Compiles the " + twirlSource + "."); - twirlCompile.getTwirlClasspath().setFrom(twirlConfiguration); - twirlCompile.setSource(twirlSource); - twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); - twirlCompile.getTemplateImports().convention(twirlSource.getTemplateImports()); - twirlCompile.getSourceEncoding().convention(twirlSource.getSourceEncoding()); - twirlCompile - .getConstructorAnnotations() - .convention(twirlSource.getConstructorAnnotations()); - DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); - twirlCompile - .getDestinationDirectory() - .convention( - buildDirectory.dir( - "generated/sources/" - + twirlSource.getName() - + "/" - + sourceSet.getName())); - }); + return project + .getTasks() + .register( + sourceSet.getCompileTaskName("twirl"), + TwirlCompile.class, + twirlCompile -> { + twirlCompile.setDescription("Compiles the " + twirlSource + "."); + twirlCompile.getTwirlClasspath().setFrom(twirlConfiguration); + twirlCompile.setSource(twirlSource); + twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); + twirlCompile.getTemplateImports().convention(twirlSource.getTemplateImports()); + twirlCompile.getSourceEncoding().convention(twirlSource.getSourceEncoding()); + twirlCompile + .getConstructorAnnotations() + .convention(twirlSource.getConstructorAnnotations()); + DirectoryProperty buildDirectory = project.getLayout().getBuildDirectory(); + twirlCompile + .getDestinationDirectory() + .convention( + buildDirectory.dir( + "generated/sources/" + + twirlSource.getName() + + "/" + + sourceSet.getName())); + }); } private TwirlSourceDirectorySet getTwirlSourceDirectorySet(SourceSet sourceSet) { From cc67a6070a1b170680fec3b1a75cc4fcfba59981 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Thu, 31 Aug 2023 16:35:11 +0300 Subject: [PATCH 22/35] [Gradle] Add few unit tests --- gradle-twirl/build.gradle.kts | 1 + .../java/play/twirl/gradle/TwirlPlugin.java | 2 +- .../play/twirl/gradle/TwirlPluginTest.java | 84 ++++++++++++++++--- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 7bb9cd77..fe0a7ee5 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -33,6 +33,7 @@ repositories { dependencies { compileOnly("com.typesafe.play:twirl-compiler_2.13:$compilerVersion") + testImplementation("org.assertj:assertj-core:3.24.2") } tasks.jar { diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index d8ef2efd..3b4d0c8f 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -149,7 +149,7 @@ private TwirlSourceDirectorySet getTwirlSourceDirectorySet(SourceSet sourceSet) return twirlSourceDirectorySet; } - private static JavaPluginExtension javaPluginExtension(Project project) { + static JavaPluginExtension javaPluginExtension(Project project) { return extensionOf(project, JavaPluginExtension.class); } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index e89e2ed8..316f79db 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -3,23 +3,85 @@ */ package play.twirl.gradle; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; +import static play.twirl.gradle.TwirlPlugin.javaPluginExtension; +import java.nio.charset.StandardCharsets; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.internal.artifacts.configurations.DefaultConfiguration; +import org.gradle.api.internal.project.DefaultProject; +import org.gradle.api.tasks.SourceSet; import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -/** A simple unit test for the 'com.playframework.twirl' plugin. */ +/** A simple unit test to check a Twirl Gradle Plugin */ class TwirlPluginTest { + + private Project project; + + @BeforeEach + void init() { + project = ProjectBuilder.builder().build(); + project.getPluginManager().apply("application"); + project.getPluginManager().apply("com.typesafe.play.twirl"); + ((DefaultProject) project).evaluate(); + } + + @Test + @DisplayName("Twirl extension should be registered") + void extensionShouldBeRegistered() { + TwirlExtension ext = (TwirlExtension) project.getExtensions().findByName("twirl"); + assertThat(ext).isNotNull(); + assertThat((ext).getScalaVersion().getOrNull()).isEqualTo("2.13"); + } + + @Test + @DisplayName("Twirl configuration should be registered") + void configurationShouldBeRegistered() { + Configuration conf = project.getConfigurations().findByName("twirl"); + assertThat(conf).isNotNull(); + assertThat(conf.isTransitive()).isTrue(); + assertThat(conf.isVisible()).isFalse(); + ((DefaultConfiguration) conf).runDependencyActions(); + assertThat(conf.getDependencies()) + .anyMatch( + dependency -> + "com.typesafe.play".equals(dependency.getGroup()) + && dependency.getName().startsWith("twirl-compiler")); + } + @Test - void pluginRegistersATask() { - // Create a test project and apply the plugin - Project project = ProjectBuilder.builder().build(); - project.getPlugins().apply("application"); - project.getPlugins().apply("com.typesafe.play.twirl"); - - // Verify the result - assertNotNull(project.getTasks().findByName("compileTwirl")); - assertNotNull(project.getTasks().findByName("compileTestTwirl")); + @DisplayName("Twirl source directory set should be registered for main source set") + void sourceDirectorySetShouldBeRegisteredForMainSourceSet() { + checkSourceDirectorySet(javaPluginExtension(project).getSourceSets().getByName("main")); + } + + @Test + @DisplayName("Twirl source directory set should be registered for test source set") + void sourceDirectorySetShouldBeRegisteredForTestSourceSet() { + checkSourceDirectorySet(javaPluginExtension(project).getSourceSets().getByName("test")); + } + + @Test + @DisplayName("Twirl compile task should be registered for main/test source sets") + void compileTasksShouldBeRegistered() { + assertThat(project.getTasks().findByName("compileTwirl")).isNotNull(); + assertThat(project.getTasks().findByName("compileTestTwirl")).isNotNull(); + } + + private void checkSourceDirectorySet(SourceSet sourceSet) { + assertThat(sourceSet).isNotNull(); + TwirlSourceDirectorySet twirlSourceSet = + sourceSet.getExtensions().findByType(TwirlSourceDirectorySet.class); + assertThat(twirlSourceSet).isNotNull(); + assertThat(twirlSourceSet.getName()).isEqualTo("twirl"); + assertThat(twirlSourceSet.getSourceEncoding().getOrNull()) + .isEqualTo(StandardCharsets.UTF_8.name()); + assertThat(twirlSourceSet.getTemplateImports().get()).isEmpty(); + assertThat(twirlSourceSet.getConstructorAnnotations().get()).isEmpty(); + assertThat(twirlSourceSet.getTemplateFormats().get()).containsKeys("html", "txt", "js", "xml"); } } From e069ad2330d5e7b0f80737d25a6eea4155c63e4c Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Thu, 31 Aug 2023 17:30:23 +0300 Subject: [PATCH 23/35] [Gradle] Drop unnecessary using `afterEvaluate` --- .../src/main/java/play/twirl/gradle/TwirlPlugin.java | 6 +++--- .../src/test/java/play/twirl/gradle/TwirlPluginTest.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 3b4d0c8f..b0f3adb7 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -67,8 +67,8 @@ private Configuration createDefaultTwirlConfiguration( conf.setDescription("The Twirl compiler library."); conf.setVisible(false); conf.setTransitive(true); - project.afterEvaluate( - __ -> { + conf.defaultDependencies( + dependencies -> { Dependency twirlCompiler = project .getDependencies() @@ -76,7 +76,7 @@ private Configuration createDefaultTwirlConfiguration( String.format( "com.typesafe.play:twirl-compiler_%s:%s", twirlExtension.getScalaVersion().get(), getDefaultTwirlVersion())); - conf.defaultDependencies(dependencies -> dependencies.add(twirlCompiler)); + dependencies.add(twirlCompiler); }); return conf; } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index 316f79db..6425fdc9 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -10,7 +10,6 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.internal.artifacts.configurations.DefaultConfiguration; -import org.gradle.api.internal.project.DefaultProject; import org.gradle.api.tasks.SourceSet; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.BeforeEach; @@ -27,7 +26,6 @@ void init() { project = ProjectBuilder.builder().build(); project.getPluginManager().apply("application"); project.getPluginManager().apply("com.typesafe.play.twirl"); - ((DefaultProject) project).evaluate(); } @Test From dc783f17a56c4b2f2edd490e737e0812e6cb64ce Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 1 Sep 2023 19:11:26 +0300 Subject: [PATCH 24/35] [Gradle] Add functional tests --- gradle-twirl/build.gradle.kts | 29 ++---- .../gradle/TwirlPluginFunctionalTest.java | 48 ---------- .../java/play/twirl/gradle/TwirlPlugin.java | 4 +- .../twirl/gradle/AbstractFunctionalTest.java | 93 +++++++++++++++++++ .../gradle/TwirlPluginFunctionalTest.java | 61 ++++++++++++ .../resources/simple/build.gradle.kts.ftlh | 17 ++++ .../simple/src/main/twirl/a/b/c.scala.html | 6 ++ scripts/test-code.sh | 4 +- 8 files changed, 190 insertions(+), 72 deletions(-) delete mode 100644 gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java create mode 100644 gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java create mode 100644 gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java create mode 100644 gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh create mode 100644 gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index fe0a7ee5..c142d10e 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -34,6 +34,8 @@ repositories { dependencies { compileOnly("com.typesafe.play:twirl-compiler_2.13:$compilerVersion") testImplementation("org.assertj:assertj-core:3.24.2") + testImplementation("commons-io:commons-io:2.13.0") + testImplementation("org.freemarker:freemarker:2.3.32") } tasks.jar { @@ -44,23 +46,17 @@ tasks.jar { testing { suites { - // Configure the built-in test suite val test by getting(JvmTestSuite::class) { - // Use JUnit Jupiter test framework useJUnitJupiter("5.9.1") - } - - // Create a new test suite - val functionalTest by registering(JvmTestSuite::class) { - dependencies { - // functionalTest test suite depends on the production code in tests - implementation(project()) - } - targets { all { - // This test suite should run after the built-in test suite has run its tests - testTask.configure { shouldRunAfter(test) } + testTask.configure { + systemProperty("twirl.version", compilerVersion) + project.findProperty("scala.version")?.let { scalaVersion -> + val ver = (scalaVersion as String).trimEnd { !it.isDigit() } + systemProperty("scala.version", ver) + } + } } } } @@ -94,8 +90,6 @@ gradlePlugin { } } -gradlePlugin.testSourceSets.add(sourceSets["functionalTest"]) - val headerLicense = "Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " val headerLicenseHash = "# $headerLicense" val headerLicenseJava = "/*\n * $headerLicense\n */" @@ -114,8 +108,3 @@ spotless { licenseHeader(headerLicenseHash, "[^#]") } } - -tasks.named("check") { - // Include functionalTest as part of the check lifecycle - dependsOn(testing.suites.named("functionalTest")) -} diff --git a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java deleted file mode 100644 index 45a57128..00000000 --- a/gradle-twirl/src/functionalTest/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ -package play.twirl.gradle; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import org.gradle.testkit.runner.BuildResult; -import org.gradle.testkit.runner.GradleRunner; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -/** A simple functional test for the 'com.playframework.twirl' plugin. */ -class TwirlPluginFunctionalTest { - @TempDir File projectDir; - - private File getBuildFile() { - return new File(projectDir, "build.gradle"); - } - - private File getSettingsFile() { - return new File(projectDir, "settings.gradle"); - } - - @Test - void canRunTask() throws IOException { - writeString(getSettingsFile(), ""); - writeString( - getBuildFile(), - "plugins {" + "id 'application'\n" + " id('com.typesafe.play.twirl')" + "}"); - - // Run the build - GradleRunner runner = GradleRunner.create(); - runner.forwardOutput(); - runner.withPluginClasspath(); - runner.withArguments("compileTwirl"); - runner.withProjectDir(projectDir); - BuildResult result = runner.build(); - } - - private void writeString(File file, String string) throws IOException { - try (Writer writer = new FileWriter(file)) { - writer.write(string); - } - } -} diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index b0f3adb7..7c7b33a0 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -24,7 +24,7 @@ /** A simple 'hello world' plugin. */ public class TwirlPlugin implements Plugin { - private static final String DEFAULT_SCALA_VERSION = "2.13"; + static final String DEFAULT_SCALA_VERSION = "2.13"; private static final Map DEFAULT_TEMPLATE_FORMATS = Map.of( @@ -58,7 +58,7 @@ public void apply(final Project project) { /** Get Twirl version from Gradle Plugin MANIFEST.MF */ private String getDefaultTwirlVersion() { - return getClass().getPackage().getImplementationVersion(); + return System.getProperty("twirl.version", getClass().getPackage().getImplementationVersion()); } private Configuration createDefaultTwirlConfiguration( diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java new file mode 100644 index 00000000..c52bae43 --- /dev/null +++ b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ +package play.twirl.gradle; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +abstract class AbstractFunctionalTest { + + @TempDir File projectDir; + + File projectSourceDir; + + GradleRunner runner; + + Configuration freemarkerConf; + + protected abstract File getProjectSourceDir(); + + protected abstract String getBuildFileContent(); + + protected String getSettingsFileContent() { + return ""; + } + + static String getScalaVersion() { + return System.getProperty("scala.version", TwirlPlugin.DEFAULT_SCALA_VERSION); + } + + static String getTwirlVersion() { + return System.getProperty("twirl.version"); + } + + protected Path projectSourcePath(String path) { + return Paths.get(projectSourceDir.getAbsolutePath(), path); + } + + protected Path projectPath(String path) { + return Paths.get(projectDir.getAbsolutePath(), path); + } + + protected Path projectBuildPath(String path) { + return Paths.get(projectDir.getAbsolutePath(), "build/" + path); + } + + @BeforeEach + void init() throws IOException, TemplateException { + projectSourceDir = getProjectSourceDir(); + runner = GradleRunner.create().withProjectDir(projectDir).withPluginClasspath().forwardOutput(); + + initFreemarker(); + + FileUtils.writeStringToFile( + projectPath("build.gradle.kts").toFile(), getBuildFileContent(), UTF_8); + FileUtils.writeStringToFile( + projectPath("settings.gradle.kts").toFile(), getSettingsFileContent(), UTF_8); + } + + protected void initFreemarker() throws IOException { + freemarkerConf = new Configuration(Configuration.VERSION_2_3_32); + freemarkerConf.setDirectoryForTemplateLoading(projectSourceDir); + } + + protected String templateProcess(String template, Map params) { + StringWriter writer = new StringWriter(); + try { + Template buildGradle = freemarkerConf.getTemplate(template); + buildGradle.process(params, writer); + } catch (Exception e) { + throw new RuntimeException(e); + } + return writer.toString(); + } + + protected BuildResult build(String... args) { + return runner.withArguments(args).build(); + } +} diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java new file mode 100644 index 00000000..d78c6032 --- /dev/null +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ +package play.twirl.gradle; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.apache.groovy.util.Maps; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** A simple functional test to check a Twirl Gradle Plugin. */ +public class TwirlPluginFunctionalTest extends AbstractFunctionalTest { + + @Override + protected File getProjectSourceDir() { + return new File("src/test/resources/simple"); + } + + @Override + protected String getBuildFileContent() { + Map params = + Maps.of( + "scalaVersion", getScalaVersion(), + "twirlVersion", getTwirlVersion()); + return templateProcess("build.gradle.kts.ftlh", params); + } + + @Test + @DisplayName("Test simple Gradle project with Twirl HTML template") + void testSimpleGradleProject() throws IOException { + File simpleSources = projectPath("src").toFile(); + FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); + + BuildResult result = build("build"); + + BuildTask compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .isNotEmptyFile(); + + BuildTask compileScalaResult = result.task(":compileScala"); + assertThat(compileScalaResult).isNotNull(); + assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(projectBuildPath("classes/scala/main/a/b/html/c.class")).isNotEmptyFile(); + + result = build("build"); + + compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); + } +} diff --git a/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh b/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh new file mode 100644 index 00000000..0425e83b --- /dev/null +++ b/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh @@ -0,0 +1,17 @@ +plugins { + application + id("com.typesafe.play.twirl") +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + implementation("com.typesafe.play:twirl-api_${scalaVersion}:${twirlVersion}") +} + +twirl { + scalaVersion.set("${scalaVersion}") +} diff --git a/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html b/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html new file mode 100644 index 00000000..7f0f12e5 --- /dev/null +++ b/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html @@ -0,0 +1,6 @@ +@**************************************************************************************************************************************************** + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * + ****************************************************************************************************************************************************@ + +@(name: String) +Hello, @name. diff --git a/scripts/test-code.sh b/scripts/test-code.sh index 0a35fa41..6581fdc8 100755 --- a/scripts/test-code.sh +++ b/scripts/test-code.sh @@ -3,5 +3,5 @@ # Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. sbt "++$MATRIX_SCALA test; mavenPlugin/scripted" || exit 1 -sbt +publishLocal +compiler/publishM2 plugin/test plugin/scripted || exit 1 -(cd gradle-twirl && ./gradlew clean check -x spotlessCheck --no-daemon) || exit 1 +sbt +publishLocal +compiler/publishM2 +apiJVM/publishM2 plugin/test plugin/scripted || exit 1 +(cd gradle-twirl && ./gradlew clean check -x spotlessCheck --no-daemon -Pscala.version="$MATRIX_SCALA") || exit 1 From f6bf96fcc84c5743bcd4a97a7d089346d23da65f Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 6 Sep 2023 15:53:38 +0300 Subject: [PATCH 25/35] [Gradle] Support Gradle 7.1+ --- .../java/play/twirl/gradle/TwirlPlugin.java | 18 ++++++++++++++---- .../DefaultTwirlSourceDirectorySet.java | 13 +++++++++++++ .../Gradle7TwirlSourceDirectorySet.java | 18 ++++++++++++++++++ .../twirl/gradle/AbstractFunctionalTest.java | 16 ++++++++++++---- .../gradle/TwirlPluginFunctionalTest.java | 17 +++++++++++------ .../resources/simple/build.gradle.kts.ftlh | 9 +++++++++ .../simple/src/main/twirl/a/b/c.scala.html | 1 + 7 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 7c7b33a0..96b5befe 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -19,7 +19,9 @@ import org.gradle.api.tasks.ScalaSourceDirectorySet; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; +import org.gradle.util.GradleVersion; import play.twirl.gradle.internal.DefaultTwirlSourceDirectorySet; +import play.twirl.gradle.internal.Gradle7TwirlSourceDirectorySet; /** A simple 'hello world' plugin. */ public class TwirlPlugin implements Plugin { @@ -140,10 +142,18 @@ private TaskProvider createTwirlCompileTask( private TwirlSourceDirectorySet getTwirlSourceDirectorySet(SourceSet sourceSet) { String displayName = ((DefaultSourceSet) sourceSet).getDisplayName(); - TwirlSourceDirectorySet twirlSourceDirectorySet = - objectFactory.newInstance( - DefaultTwirlSourceDirectorySet.class, - objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); + TwirlSourceDirectorySet twirlSourceDirectorySet; + if (GradleVersion.current().compareTo(GradleVersion.version("8.0")) < 0) { // Gradle < 8.0 + twirlSourceDirectorySet = + objectFactory.newInstance( + Gradle7TwirlSourceDirectorySet.class, + objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); + } else { // Gradle 8+ + twirlSourceDirectorySet = + objectFactory.newInstance( + DefaultTwirlSourceDirectorySet.class, + objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); + } twirlSourceDirectorySet.getFilter().include("**/*.scala.*"); twirlSourceDirectorySet.getTemplateFormats().convention(DEFAULT_TEMPLATE_FORMATS); return twirlSourceDirectorySet; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index 83c360b5..553a7145 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -39,6 +39,19 @@ public DefaultTwirlSourceDirectorySet( this.sourceEncoding = objectFactory.property(String.class).convention(UTF_8.name()); } + /** + * @deprecated Constructor for support Gradle 7.x + */ + @Deprecated + public DefaultTwirlSourceDirectorySet( + SourceDirectorySet sourceDirectorySet, ObjectFactory objectFactory) { + super(sourceDirectorySet); + this.templateFormats = objectFactory.mapProperty(String.class, String.class); + this.templateImports = objectFactory.setProperty(String.class); + this.constructorAnnotations = objectFactory.listProperty(String.class); + this.sourceEncoding = objectFactory.property(String.class).convention(UTF_8.name()); + } + @Override public MapProperty getTemplateFormats() { return templateFormats; diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java new file mode 100644 index 00000000..f79330e6 --- /dev/null +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ +package play.twirl.gradle.internal; + +import javax.inject.Inject; +import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.model.ObjectFactory; + +@Deprecated +public class Gradle7TwirlSourceDirectorySet extends DefaultTwirlSourceDirectorySet { + + @Inject + public Gradle7TwirlSourceDirectorySet( + SourceDirectorySet sourceDirectorySet, ObjectFactory objectFactory) { + super(sourceDirectorySet, objectFactory); + } +} diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java index c52bae43..7396a90a 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java @@ -7,13 +7,13 @@ import freemarker.template.Configuration; import freemarker.template.Template; -import freemarker.template.TemplateException; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; @@ -59,7 +59,7 @@ protected Path projectBuildPath(String path) { } @BeforeEach - void init() throws IOException, TemplateException { + void init() throws IOException { projectSourceDir = getProjectSourceDir(); runner = GradleRunner.create().withProjectDir(projectDir).withPluginClasspath().forwardOutput(); @@ -87,7 +87,15 @@ protected String templateProcess(String template, Map params) { return writer.toString(); } - protected BuildResult build(String... args) { - return runner.withArguments(args).build(); + protected BuildResult build(String gradleVersion, String... args) { + return runner.withGradleVersion(gradleVersion).withArguments(args).build(); + } + + static Stream gradleVersions() { + // https://docs.gradle.org/current/userguide/scala_plugin.html#sec:configure_zinc_compiler + if (getScalaVersion().equals("3")) { // Gradle 7.5+ + return Stream.of("7.6.2", "8.0.2", "8.3"); + } + return Stream.of("7.1.1", "7.6.2", "8.0.2", "8.3"); } } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java index d78c6032..de5cb1e9 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java @@ -14,7 +14,8 @@ import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** A simple functional test to check a Twirl Gradle Plugin. */ public class TwirlPluginFunctionalTest extends AbstractFunctionalTest { @@ -33,26 +34,30 @@ protected String getBuildFileContent() { return templateProcess("build.gradle.kts.ftlh", params); } - @Test + @ParameterizedTest + @MethodSource("gradleVersions") @DisplayName("Test simple Gradle project with Twirl HTML template") - void testSimpleGradleProject() throws IOException { + void testSimpleGradleProject(String gradleVersion) throws IOException { File simpleSources = projectPath("src").toFile(); FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); - BuildResult result = build("build"); + BuildResult result = build(gradleVersion, "build"); BuildTask compileTwirlResult = result.task(":compileTwirl"); assertThat(compileTwirlResult).isNotNull(); assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) - .isNotEmptyFile(); + .isNotEmptyFile() + .binaryContent() + .asString() + .contains("import java.lang._", "class c @java.lang.Deprecated()"); BuildTask compileScalaResult = result.task(":compileScala"); assertThat(compileScalaResult).isNotNull(); assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(projectBuildPath("classes/scala/main/a/b/html/c.class")).isNotEmptyFile(); - result = build("build"); + result = build(gradleVersion, "build"); compileTwirlResult = result.task(":compileTwirl"); assertThat(compileTwirlResult).isNotNull(); diff --git a/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh b/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh index 0425e83b..8732cfe5 100644 --- a/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh +++ b/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh @@ -15,3 +15,12 @@ dependencies { twirl { scalaVersion.set("${scalaVersion}") } + +sourceSets { + main { + twirl { + templateImports.add("java.lang._") + constructorAnnotations.add("@java.lang.Deprecated()") + } + } +} diff --git a/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html b/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html index 7f0f12e5..31770d9d 100644 --- a/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html +++ b/gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html @@ -2,5 +2,6 @@ * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * ****************************************************************************************************************************************************@ +@this(str: String) @(name: String) Hello, @name. From 38518a0b94af8aa560ea9c146da4f8a725f07c91 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Thu, 7 Sep 2023 13:38:55 +0300 Subject: [PATCH 26/35] [Gradle] Separate publish release action to be able to re-run separately --- .github/workflows/publish.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9f4d846a..7b5631dd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,14 +22,20 @@ jobs: ./gradlew --no-daemon publishToSonatype -x test -PsonatypeUsername="$SONATYPE_USERNAME" -PsonatypePassword="$SONATYPE_PASSWORD" secrets: inherit - publish-release-artifacts: + publish-release: name: Publish / Artifacts if: github.ref_type == 'tag' # Releases uses: playframework/.github/.github/workflows/publish.yml@v3 + secrets: inherit + + publish-release-gradle: + name: Publish / Artifacts (Gradle Plugin) + if: github.ref_type == 'tag' # Releases + uses: playframework/.github/.github/workflows/publish.yml@v3 with: gradle-build-root: gradle-twirl cmd: | - sbt +compiler/publishM2 ci-release + sbt +compiler/publishM2 cd gradle-twirl ./gradlew --no-daemon publishPlugins -x test -Pgradle.publish.key="$GRADLE_PUBLISH_KEY" -Pgradle.publish.secret="$GRADLE_PUBLISH_SECRET" secrets: inherit From f6a985b89e3e7380b2d51e67a37aef81f009e274 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Thu, 7 Sep 2023 17:49:17 +0300 Subject: [PATCH 27/35] [Gradle] Migrate to Gradle Version Catalog --- gradle-twirl/build.gradle.kts | 22 +++++++++++----------- gradle-twirl/gradle/libs.versions.toml | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 gradle-twirl/gradle/libs.versions.toml diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index c142d10e..cbda535d 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -7,9 +7,9 @@ import java.util.Properties import kotlin.text.Charsets.UTF_8 plugins { - id("com.gradle.plugin-publish") version "1.2.0" - id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - id("com.diffplug.spotless") version "6.19.0" + alias(libs.plugins.gradle.plugin.publish) + alias(libs.plugins.nexus.publish) + alias(libs.plugins.spotless) signing } @@ -21,21 +21,20 @@ val compilerVersion: String = if (this.getProperty("twirl.compiler.version").isNullOrEmpty()) throw GradleException("`twirl.compiler.version` key didn't find in ${file.absolutePath}") }.getProperty("twirl.compiler.version") -// group = "com.playframework" // group and plugin id must use same top level namespace +// group = "org.playframework" // group and plugin id must use same top level namespace group = "com.typesafe.play" // TODO: uncomment line above and remove this version = compilerVersion repositories { - // Use Maven Central for resolving dependencies. mavenCentral() mavenLocal() } dependencies { compileOnly("com.typesafe.play:twirl-compiler_2.13:$compilerVersion") - testImplementation("org.assertj:assertj-core:3.24.2") - testImplementation("commons-io:commons-io:2.13.0") - testImplementation("org.freemarker:freemarker:2.3.32") + testImplementation(libs.assertj) + testImplementation(libs.commons.io) + testImplementation(libs.freemarker) } tasks.jar { @@ -44,10 +43,11 @@ tasks.jar { } } +@Suppress("UnstableApiUsage") testing { suites { val test by getting(JvmTestSuite::class) { - useJUnitJupiter("5.9.1") + useJUnitJupiter() targets { all { testTask.configure { @@ -77,12 +77,12 @@ nexusPublishing { } } +@Suppress("UnstableApiUsage") gradlePlugin { website.set("https://www.playframework.com/documentation/latest/ScalaTemplates") vcsUrl.set("https://github.com/playframework/twirl") - // Define the plugin val twirl by plugins.creating { - id = "com.typesafe.play.twirl" // TODO: rename to "com.playframework.twirl" + id = "com.typesafe.play.twirl" // TODO: rename to "org.playframework.twirl" displayName = "Twirl Plugin" description = "A Gradle plugin to compile Twirl templates" tags.set(listOf("playframework", "web", "template", "java", "scala")) diff --git a/gradle-twirl/gradle/libs.versions.toml b/gradle-twirl/gradle/libs.versions.toml new file mode 100644 index 00000000..a2d23ad3 --- /dev/null +++ b/gradle-twirl/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +[versions] +gradle-plugin-publish = "1.2.0" +nexus-publish = "1.3.0" +spotless = "6.19.0" +assertj = "3.24.2" +commons-io = "2.13.0" +freemarker = "2.3.32" + +[libraries] +assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" } +commons-io = { group = "commons-io", name = "commons-io", version.ref = "commons-io" } +freemarker = { group = "org.freemarker", name = "freemarker", version.ref = "freemarker" } + +[plugins] +gradle-plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" } +nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } From 11d0c685ff783108e61c9838bff168dbe25b84de Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Thu, 7 Sep 2023 19:23:58 +0300 Subject: [PATCH 28/35] [Gradle] Add info into README --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/README.md b/README.md index 0b0cbea7..a889c253 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,108 @@ To use a snapshot version add the [Sonatype Snapshot repository](https://oss.son ``` +## gradle-twirl + +⚠️ `com.typesafe.play.twirl` plugin requires Gradle 7.1 or higher. + +To use the Twirl plugin in your project add the gradle plugin and +Twirl API as a dependency into `build.gradle.kts`: + +```kotlin +plugins { + ... + id("com.typesafe.play.twirl") version "LATEST_VERSION" +} + +dependencies { + implementation("com.typesafe.play", "twirl-api_${scalaVersion}", "LATEST_VERSION") +} +``` + +Replacing the `LATEST_VERSION` with the latest version published, which should be [![Latest version](https://index.scala-lang.org/playframework/twirl/twirl-api/latest.svg?color=orange)](https://index.scala-lang.org/playframework/twirl/twirl-api). + +### Template files + +Twirl template files are expected to be placed under `src/main/twirl` or +`src/test/twirl`, similar to `scala` or `java` sources. The additional source +locations for template files can be configured. + +Template files must be named `{name}.scala.{ext}` where `ext` can be `html`, +`js`, `xml`, or `txt`. + +### Additional imports + +To add additional imports for the Scala code in template files, use the +`templateImports` key. For example: + +```kotlin +sourceSets { + main { + twirl { + templateImports.add("org.example._") + } + } +} +``` + +### Source directories + +To configure the source directories where template files will be found, use the +`srcDir` method for [SourceDirectorySet](https://docs.gradle.org/current/javadoc/org/gradle/api/file/SourceDirectorySet.html). For example: + +```kotlin +sourceSets { + main { + twirl { + srcDir("app") + } + } +} +``` + +### Scala version + +To configure the Scala version use the `scalaVersion` property of [TwirlExtension](gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java) (`2.13` by default). For example: + +```kotlin +twirl { + scalaVersion.set("3") +} +``` + +### Other properties + +Also, you can use the next properties: + +```kotlin +sourceSets { + main { + twirl { + // Annotations added to constructors in injectable templates + constructorAnnotations.add("@org.example.MyAnnotation()") + // Defined custom twirl template formats + templateFormats.put("csv", "play.twirl.api.TxtFormat") + // Source encoding for template files and generated scala files + sourceEncoding.set("") + } + } +} +``` + +### Snapshots + +To use a snapshot version add the [Sonatype Snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/com/typesafe/play/twirl/com.typesafe.play.twirl.gradle.plugin/) into `settings.gradle.kts`: + +```kotlin +pluginManagement { + repositories { + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + } + } +} +``` + ## Releasing a new version See https://github.com/playframework/.github/blob/main/RELEASING.md From 03532f2438349ca073d2e80d33bb07fabaa9b1d0 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Fri, 8 Sep 2023 13:15:00 +0300 Subject: [PATCH 29/35] [Gradle] Add javadoc comments --- .../java/play/twirl/gradle/TwirlCompile.java | 1 + .../play/twirl/gradle/TwirlExtension.java | 13 +++++ .../java/play/twirl/gradle/TwirlPlugin.java | 27 +++++----- .../twirl/gradle/TwirlSourceDirectorySet.java | 52 +++++++++++++++++++ .../DefaultTwirlSourceDirectorySet.java | 1 + .../Gradle7TwirlSourceDirectorySet.java | 7 +++ .../gradle/internal/TwirlCompileAction.java | 1 + .../gradle/internal/TwirlCompileParams.java | 1 + 8 files changed, 89 insertions(+), 14 deletions(-) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index 71eff915..ed38d79c 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -26,6 +26,7 @@ import org.gradle.workers.WorkerExecutor; import play.twirl.gradle.internal.TwirlCompileAction; +/** Gradle task for compiling Twirl templates into Scala code. */ public abstract class TwirlCompile extends SourceTask { @InputFiles diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java index a4728193..77de6893 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java @@ -5,7 +5,20 @@ import org.gradle.api.provider.Property; +/** + * The extension of the plugin allowing for configuring the target Scala version used for the + * application. + */ public abstract class TwirlExtension { + /** + * Scala version used for compilation Twirl templates. + * + *

{@code
+   * twirl {
+   *   scalaVersion.set("3")
+   * }
+   * }
+ */ public abstract Property getScalaVersion(); } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 96b5befe..5e7215f6 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -23,7 +23,7 @@ import play.twirl.gradle.internal.DefaultTwirlSourceDirectorySet; import play.twirl.gradle.internal.Gradle7TwirlSourceDirectorySet; -/** A simple 'hello world' plugin. */ +/** A Gradle plugin to compile Twirl templates. */ public class TwirlPlugin implements Plugin { static final String DEFAULT_SCALA_VERSION = "2.13"; @@ -58,7 +58,7 @@ public void apply(final Project project) { configureSourceSetDefaults(project, twirlConfiguration); } - /** Get Twirl version from Gradle Plugin MANIFEST.MF */ + /** Get Twirl compiler version from Gradle Plugin MANIFEST.MF */ private String getDefaultTwirlVersion() { return System.getProperty("twirl.version", getClass().getPackage().getImplementationVersion()); } @@ -140,25 +140,24 @@ private TaskProvider createTwirlCompileTask( }); } + @SuppressWarnings("deprecation") private TwirlSourceDirectorySet getTwirlSourceDirectorySet(SourceSet sourceSet) { String displayName = ((DefaultSourceSet) sourceSet).getDisplayName(); - TwirlSourceDirectorySet twirlSourceDirectorySet; - if (GradleVersion.current().compareTo(GradleVersion.version("8.0")) < 0) { // Gradle < 8.0 - twirlSourceDirectorySet = - objectFactory.newInstance( - Gradle7TwirlSourceDirectorySet.class, - objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); - } else { // Gradle 8+ - twirlSourceDirectorySet = - objectFactory.newInstance( - DefaultTwirlSourceDirectorySet.class, - objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); - } + TwirlSourceDirectorySet twirlSourceDirectorySet = + objectFactory.newInstance( + isGradleVersionLessThan("8.0") + ? Gradle7TwirlSourceDirectorySet.class + : DefaultTwirlSourceDirectorySet.class, + objectFactory.sourceDirectorySet("twirl", displayName + " Twirl source")); twirlSourceDirectorySet.getFilter().include("**/*.scala.*"); twirlSourceDirectorySet.getTemplateFormats().convention(DEFAULT_TEMPLATE_FORMATS); return twirlSourceDirectorySet; } + static boolean isGradleVersionLessThan(String gradleVersion) { + return GradleVersion.current().compareTo(GradleVersion.version(gradleVersion)) < 0; + } + static JavaPluginExtension javaPluginExtension(Project project) { return extensionOf(project, JavaPluginExtension.class); } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java index b767bd98..b7959f48 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java @@ -15,11 +15,63 @@ */ public interface TwirlSourceDirectorySet extends SourceDirectorySet { + /** + * Custom template formats configured for this source directory set. + * + *
{@code
+   * sourceSets {
+   *   main {
+   *     twirl {
+   *       templateFormats.put("csv", "play.twirl.api.TxtFormat")
+   *     }
+   *   }
+   * }
+   * }
+ */ MapProperty getTemplateFormats(); + /** + * Imports that should be added to generated source files. + * + *
{@code
+   * sourceSets {
+   *   main {
+   *     twirl {
+   *       templateImports.add("org.example._")
+   *     }
+   *   }
+   * }
+   * }
+ */ SetProperty getTemplateImports(); + /** + * Annotations added to constructors in injectable templates. + * + *
{@code
+   * sourceSets {
+   *   main {
+   *     twirl {
+   *       constructorAnnotations.add("@org.example.MyAnnotation()")
+   *     }
+   *   }
+   * }
+   * }
+ */ ListProperty getConstructorAnnotations(); + /** + * Source encoding for template files and generated scala files. + * + *
{@code
+   * sourceSets {
+   *   main {
+   *     twirl {
+   *       sourceEncoding.set("")
+   *     }
+   *   }
+   * }
+   * }
+ */ Property getSourceEncoding(); } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java index 553a7145..3b775e23 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java @@ -16,6 +16,7 @@ import org.gradle.api.provider.SetProperty; import play.twirl.gradle.TwirlSourceDirectorySet; +/** Default implementation of {@link TwirlSourceDirectorySet}. */ public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet implements TwirlSourceDirectorySet { diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java index f79330e6..8bb083d8 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java @@ -6,8 +6,15 @@ import javax.inject.Inject; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.model.ObjectFactory; +import play.twirl.gradle.TwirlSourceDirectorySet; +/** + * Implementation of {@link TwirlSourceDirectorySet} is needed to support Gradle 7. + * + * @deprecated + */ @Deprecated +@SuppressWarnings("DeprecatedIsStillUsed") public class Gradle7TwirlSourceDirectorySet extends DefaultTwirlSourceDirectorySet { @Inject diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java index 7ba573ea..1fd642fe 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java @@ -12,6 +12,7 @@ import play.japi.twirl.compiler.TwirlCompiler; import scala.io.Codec; +/** Gradle work action that compile one Twirl template. */ public abstract class TwirlCompileAction implements WorkAction { private static final Logger LOGGER = Logging.getLogger(TwirlCompileAction.class); diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index a3b1b973..d52911a5 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -10,6 +10,7 @@ import org.gradle.api.provider.SetProperty; import org.gradle.workers.WorkParameters; +/** Parameters of compilation work action. */ public interface TwirlCompileParams extends WorkParameters { RegularFileProperty getSourceFile(); From 3d361a7c3c17f4f8ccd1f3b8a3debcbc4134859e Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Mon, 11 Sep 2023 16:24:18 +0300 Subject: [PATCH 30/35] [Gradle] Support incremental compilation --- .../java/play/twirl/gradle/TwirlCompile.java | 49 ++++--- .../java/play/twirl/gradle/TwirlPlugin.java | 2 +- .../gradle/internal/TwirlCompileAction.java | 48 ++++++- .../gradle/internal/TwirlCompileParams.java | 3 + .../twirl/gradle/AbstractFunctionalTest.java | 12 +- .../play/twirl/gradle/SimpleProjectTest.java | 128 ++++++++++++++++++ .../gradle/TwirlPluginFunctionalTest.java | 66 --------- 7 files changed, 215 insertions(+), 93 deletions(-) create mode 100644 gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java delete mode 100644 gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index ed38d79c..c6615f85 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -4,32 +4,45 @@ package play.twirl.gradle; import java.io.File; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import javax.inject.Inject; +import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileType; +import org.gradle.api.file.RelativePath; import org.gradle.api.internal.file.RelativeFile; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.IgnoreEmptyDirectories; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.SourceTask; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; import org.gradle.internal.FileUtils; +import org.gradle.work.FileChange; +import org.gradle.work.Incremental; +import org.gradle.work.InputChanges; import org.gradle.workers.WorkQueue; import org.gradle.workers.WorkerExecutor; import play.twirl.gradle.internal.TwirlCompileAction; /** Gradle task for compiling Twirl templates into Scala code. */ -public abstract class TwirlCompile extends SourceTask { +public abstract class TwirlCompile extends DefaultTask { @InputFiles + @Incremental + @IgnoreEmptyDirectories + @PathSensitive(PathSensitivity.RELATIVE) + public abstract ConfigurableFileCollection getSource(); + + @Classpath public abstract ConfigurableFileCollection getTwirlClasspath(); @OutputDirectory @@ -51,16 +64,20 @@ public abstract class TwirlCompile extends SourceTask { public abstract WorkerExecutor getWorkerExecutor(); @TaskAction - void compile() { - WorkQueue workQueue = - getWorkerExecutor() - .classLoaderIsolation(spec -> spec.getClasspath().from(getTwirlClasspath())); + void compile(InputChanges changes) { + for (FileChange change : changes.getFileChanges(getSource())) { + if (change.getFileType() == FileType.DIRECTORY) continue; + WorkQueue workQueue = + getWorkerExecutor() + .classLoaderIsolation(spec -> spec.getClasspath().from(getTwirlClasspath())); - Map templateFormats = getTemplateFormats().get(); - for (RelativeFile sourceFile : getSourceAsRelativeFiles()) { + Map templateFormats = getTemplateFormats().get(); + RelativeFile sourceFile = + new RelativeFile(change.getFile(), RelativePath.parse(true, change.getNormalizedPath())); workQueue.submit( TwirlCompileAction.class, parameters -> { + parameters.getChangeType().set(change.getChangeType()); parameters.getSourceFile().set(sourceFile.getFile()); parameters.getSourceDirectory().set(sourceFile.getBaseDir()); parameters.getDestinationDirectory().set(getDestinationDirectory()); @@ -86,16 +103,4 @@ private String getFormatterType(Map formats, File file) { "Unknown template format of '%s'. Possible extentions: [%s]", file.getName(), String.join(", ", formats.keySet())))); } - - private Iterable getSourceAsRelativeFiles() { - List relativeFiles = new ArrayList<>(); - getSource() - .visit( - fvd -> { - if (fvd.getFile().isFile()) { - relativeFiles.add(new RelativeFile(fvd.getFile(), fvd.getRelativePath())); - } - }); - return relativeFiles; - } } diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 5e7215f6..ec873cff 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -121,7 +121,7 @@ private TaskProvider createTwirlCompileTask( twirlCompile -> { twirlCompile.setDescription("Compiles the " + twirlSource + "."); twirlCompile.getTwirlClasspath().setFrom(twirlConfiguration); - twirlCompile.setSource(twirlSource); + twirlCompile.getSource().setFrom(twirlSource); twirlCompile.getTemplateFormats().convention(twirlSource.getTemplateFormats()); twirlCompile.getTemplateImports().convention(twirlSource.getTemplateImports()); twirlCompile.getSourceEncoding().convention(twirlSource.getSourceEncoding()); diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java index 1fd642fe..bcbdd55d 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java @@ -8,17 +8,59 @@ import java.util.List; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; +import org.gradle.work.ChangeType; import org.gradle.workers.WorkAction; import play.japi.twirl.compiler.TwirlCompiler; +import play.twirl.compiler.TwirlCompiler$; import scala.io.Codec; -/** Gradle work action that compile one Twirl template. */ +/** Gradle work action that compile or delete one Twirl template. */ public abstract class TwirlCompileAction implements WorkAction { private static final Logger LOGGER = Logging.getLogger(TwirlCompileAction.class); @Override public void execute() { + if (getParameters().getChangeType().get() == ChangeType.REMOVED) { + delete(); + } else { + compile(); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private void delete() { + try { + File sourceFile = getParameters().getSourceFile().getAsFile().get(); + File sourceDirectory = getParameters().getSourceDirectory().getAsFile().get(); + File destinationDirectory = getParameters().getDestinationDirectory().getAsFile().get(); + String sourceEncoding = getParameters().getSourceEncoding().get(); + // WA: Need to create a source file temporarily for correct calculate path of compiled + // template to delete + sourceFile.createNewFile(); + File compiledTemplate = + TwirlCompiler$.MODULE$ + .generatedFile( + sourceFile, + Codec.string2codec(sourceEncoding), + sourceDirectory, + destinationDirectory, + false) + ._2 + .file(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Delete Twirl template {}", compiledTemplate.getCanonicalPath()); + } + // Delete temporary empty source file + sourceFile.delete(); + compiledTemplate.delete(); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + private void compile() { try { File sourceFile = getParameters().getSourceFile().getAsFile().get(); File sourceDirectory = getParameters().getSourceDirectory().getAsFile().get(); @@ -28,8 +70,8 @@ public void execute() { Collection imports = getParameters().getTemplateImports().get(); List constructorAnnotations = getParameters().getConstructorAnnotations().get(); String sourceEncoding = getParameters().getSourceEncoding().get(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( + if (LOGGER.isInfoEnabled()) { + LOGGER.info( "Compile Twirl template [{}/{}] {} from {} into {}", formatterType, sourceEncoding, diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java index d52911a5..1ec44e27 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java @@ -8,11 +8,14 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; +import org.gradle.work.ChangeType; import org.gradle.workers.WorkParameters; /** Parameters of compilation work action. */ public interface TwirlCompileParams extends WorkParameters { + Property getChangeType(); + RegularFileProperty getSourceFile(); DirectoryProperty getSourceDirectory(); diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java index 7396a90a..d13826b2 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.stream.Stream; import org.apache.commons.io.FileUtils; +import org.gradle.api.JavaVersion; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.BeforeEach; @@ -61,7 +62,12 @@ protected Path projectBuildPath(String path) { @BeforeEach void init() throws IOException { projectSourceDir = getProjectSourceDir(); - runner = GradleRunner.create().withProjectDir(projectDir).withPluginClasspath().forwardOutput(); + runner = + GradleRunner.create() + .withProjectDir(projectDir) + .withPluginClasspath() + .withDebug(true) + .forwardOutput(); initFreemarker(); @@ -96,6 +102,10 @@ static Stream gradleVersions() { if (getScalaVersion().equals("3")) { // Gradle 7.5+ return Stream.of("7.6.2", "8.0.2", "8.3"); } + // https://docs.gradle.org/current/userguide/compatibility.html + if (JavaVersion.current().compareTo(JavaVersion.VERSION_17) >= 0) { // Gradle 7.3+ + return Stream.of("7.6.2", "8.0.2", "8.3"); + } return Stream.of("7.1.1", "7.6.2", "8.0.2", "8.3"); } } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java new file mode 100644 index 00000000..1725fd91 --- /dev/null +++ b/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ +package play.twirl.gradle; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.apache.groovy.util.Maps; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** A simple functional test to check a Twirl Gradle Plugin. */ +@DisplayName("Simple Gradle project with Twirl HTML template") +public class SimpleProjectTest extends AbstractFunctionalTest { + + @Override + protected File getProjectSourceDir() { + return new File("src/test/resources/simple"); + } + + @Override + protected String getBuildFileContent() { + Map params = + Maps.of( + "scalaVersion", getScalaVersion(), + "twirlVersion", getTwirlVersion()); + return templateProcess("build.gradle.kts.ftlh", params); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + @DisplayName("Test common build") + void testCommonBuild(String gradleVersion) throws IOException { + File simpleSources = projectPath("src").toFile(); + FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); + + BuildResult result = build(gradleVersion, "build"); + + BuildTask compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .isNotEmptyFile() + .binaryContent() + .asString() + .contains("import java.lang._", "class c @java.lang.Deprecated()"); + + BuildTask compileScalaResult = result.task(":compileScala"); + assertThat(compileScalaResult).isNotNull(); + assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(projectBuildPath("classes/scala/main/a/b/html/c.class")).isNotEmptyFile(); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + @DisplayName("Test UP-TO-DATE behavior for build without changes in templates") + void testUpToDateBuild(String gradleVersion) throws IOException { + File simpleSources = projectPath("src").toFile(); + FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); + + build(gradleVersion, "build"); + + BuildResult result = build(gradleVersion, "build"); + + BuildTask compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); + + BuildTask compileScalaResult = result.task(":compileScala"); + assertThat(compileScalaResult).isNotNull(); + assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + @DisplayName("Test incremental compilation after add and delete template") + void testIncrementalBuild(String gradleVersion) throws IOException { + File simpleSources = projectPath("src").toFile(); + FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); + + build(gradleVersion, "build"); + + // Add new Twirl template + Path newTemplate = projectPath("src/main/twirl/a/b/d.scala.html"); + Files.copy(projectSourcePath("src/main/twirl/a/b/c.scala.html"), newTemplate); + + BuildResult result = build(gradleVersion, "build"); + + BuildTask compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .isNotEmptyFile(); + + BuildTask compileScalaResult = result.task(":compileScala"); + assertThat(compileScalaResult).isNotNull(); + assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(projectBuildPath("classes/scala/main/a/b/html/d.class")).isNotEmptyFile(); + + // Delete twirl template + Files.delete(newTemplate); + + result = build(gradleVersion, "build"); + + compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .doesNotExist(); + + compileScalaResult = result.task(":compileScala"); + assertThat(compileScalaResult).isNotNull(); + assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(projectBuildPath("classes/scala/main/a/b/html/d.class")).doesNotExist(); + } +} diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java deleted file mode 100644 index de5cb1e9..00000000 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginFunctionalTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ -package play.twirl.gradle; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.File; -import java.io.IOException; -import java.util.Map; -import org.apache.commons.io.FileUtils; -import org.apache.groovy.util.Maps; -import org.gradle.testkit.runner.BuildResult; -import org.gradle.testkit.runner.BuildTask; -import org.gradle.testkit.runner.TaskOutcome; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -/** A simple functional test to check a Twirl Gradle Plugin. */ -public class TwirlPluginFunctionalTest extends AbstractFunctionalTest { - - @Override - protected File getProjectSourceDir() { - return new File("src/test/resources/simple"); - } - - @Override - protected String getBuildFileContent() { - Map params = - Maps.of( - "scalaVersion", getScalaVersion(), - "twirlVersion", getTwirlVersion()); - return templateProcess("build.gradle.kts.ftlh", params); - } - - @ParameterizedTest - @MethodSource("gradleVersions") - @DisplayName("Test simple Gradle project with Twirl HTML template") - void testSimpleGradleProject(String gradleVersion) throws IOException { - File simpleSources = projectPath("src").toFile(); - FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); - - BuildResult result = build(gradleVersion, "build"); - - BuildTask compileTwirlResult = result.task(":compileTwirl"); - assertThat(compileTwirlResult).isNotNull(); - assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); - assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) - .isNotEmptyFile() - .binaryContent() - .asString() - .contains("import java.lang._", "class c @java.lang.Deprecated()"); - - BuildTask compileScalaResult = result.task(":compileScala"); - assertThat(compileScalaResult).isNotNull(); - assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); - assertThat(projectBuildPath("classes/scala/main/a/b/html/c.class")).isNotEmptyFile(); - - result = build(gradleVersion, "build"); - - compileTwirlResult = result.task(":compileTwirl"); - assertThat(compileTwirlResult).isNotNull(); - assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); - } -} From 8b57fce4b18d2d1c23d5d839f9db8637cc2a2dd6 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Tue, 12 Sep 2023 16:38:04 +0300 Subject: [PATCH 31/35] [Gradle] Support Build Cache for TwirlCompile task --- .../java/play/twirl/gradle/TwirlCompile.java | 2 + .../play/twirl/gradle/SimpleProjectTest.java | 65 +++++++++++++++++++ .../resources/simple/settings.gradle.kts.ftlh | 5 ++ 3 files changed, 72 insertions(+) create mode 100644 gradle-twirl/src/test/resources/simple/settings.gradle.kts.ftlh diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java index c6615f85..dbc090de 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java @@ -17,6 +17,7 @@ import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.IgnoreEmptyDirectories; import org.gradle.api.tasks.Input; @@ -34,6 +35,7 @@ import play.twirl.gradle.internal.TwirlCompileAction; /** Gradle task for compiling Twirl templates into Scala code. */ +@CacheableTask public abstract class TwirlCompile extends DefaultTask { @InputFiles diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java index 1725fd91..6731e8fc 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/SimpleProjectTest.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.groovy.util.Maps; @@ -37,6 +38,11 @@ protected String getBuildFileContent() { return templateProcess("build.gradle.kts.ftlh", params); } + @Override + protected String getSettingsFileContent() { + return templateProcess("settings.gradle.kts.ftlh", Collections.emptyMap()); + } + @ParameterizedTest @MethodSource("gradleVersions") @DisplayName("Test common build") @@ -125,4 +131,63 @@ void testIncrementalBuild(String gradleVersion) throws IOException { assertThat(compileScalaResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(projectBuildPath("classes/scala/main/a/b/html/d.class")).doesNotExist(); } + + @ParameterizedTest + @MethodSource("gradleVersions") + @DisplayName("Test build cache") + void testBuildCache(String gradleVersion) throws IOException { + File simpleSources = projectPath("src").toFile(); + FileUtils.copyDirectory(projectSourcePath("src").toFile(), simpleSources); + Path newTemplate = projectPath("src/main/twirl/a/b/d.scala.html"); + Files.copy(projectSourcePath("src/main/twirl/a/b/c.scala.html"), newTemplate); + + BuildResult result = build(gradleVersion, "--build-cache", "compileTwirl"); + + BuildTask compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .isNotEmptyFile(); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .isNotEmptyFile(); + + build(gradleVersion, "clean"); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .doesNotExist(); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .doesNotExist(); + + result = build(gradleVersion, "--build-cache", "compileTwirl"); + + compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.FROM_CACHE); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .isNotEmptyFile(); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .isNotEmptyFile(); + + build(gradleVersion, "clean"); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .doesNotExist(); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .doesNotExist(); + + Files.delete(newTemplate); + + result = build(gradleVersion, "--build-cache", "compileTwirl"); + + compileTwirlResult = result.task(":compileTwirl"); + assertThat(compileTwirlResult).isNotNull(); + assertThat(compileTwirlResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/c.template.scala")) + .isNotEmptyFile(); + assertThat(projectBuildPath("generated/sources/twirl/main/a/b/html/d.template.scala")) + .doesNotExist(); + } } diff --git a/gradle-twirl/src/test/resources/simple/settings.gradle.kts.ftlh b/gradle-twirl/src/test/resources/simple/settings.gradle.kts.ftlh new file mode 100644 index 00000000..d55ac3ca --- /dev/null +++ b/gradle-twirl/src/test/resources/simple/settings.gradle.kts.ftlh @@ -0,0 +1,5 @@ +buildCache { + local { + directory = File(rootProject.projectDir, "build-cache") + } +} From edcd87248b68238188b6d66c7e4505346cc3dd88 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Sat, 7 Oct 2023 01:17:41 +0300 Subject: [PATCH 32/35] [Gradle] Change groupId to `org.playframework.twirl` --- .github/workflows/publish.yml | 1 - README.md | 8 ++++---- gradle-twirl/build.gradle.kts | 7 +++---- gradle-twirl/gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../src/main/java/play/twirl/gradle/TwirlPlugin.java | 2 +- .../java/play/twirl/gradle/AbstractFunctionalTest.java | 6 +++--- .../src/test/java/play/twirl/gradle/TwirlPluginTest.java | 4 ++-- .../src/test/resources/simple/build.gradle.kts.ftlh | 4 ++-- 8 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7b5631dd..201f345f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,6 @@ on: push: branches: # Snapshots - main - - gradle # TODO: remove this line tags: ["**"] # Releases release: types: [published] diff --git a/README.md b/README.md index a889c253..63975317 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ To use a snapshot version add the [Sonatype Snapshot repository](https://oss.son ## gradle-twirl -⚠️ `com.typesafe.play.twirl` plugin requires Gradle 7.1 or higher. +⚠️ `org.playframework.twirl` plugin requires Gradle 7.1 or higher. To use the Twirl plugin in your project add the gradle plugin and Twirl API as a dependency into `build.gradle.kts`: @@ -232,11 +232,11 @@ Twirl API as a dependency into `build.gradle.kts`: ```kotlin plugins { ... - id("com.typesafe.play.twirl") version "LATEST_VERSION" + id("org.playframework.twirl") version "LATEST_VERSION" } dependencies { - implementation("com.typesafe.play", "twirl-api_${scalaVersion}", "LATEST_VERSION") + implementation("org.playframework.twirl", "twirl-api_${scalaVersion}", "LATEST_VERSION") } ``` @@ -312,7 +312,7 @@ sourceSets { ### Snapshots -To use a snapshot version add the [Sonatype Snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/com/typesafe/play/twirl/com.typesafe.play.twirl.gradle.plugin/) into `settings.gradle.kts`: +To use a snapshot version add the [Sonatype Snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/org/playframework/twirl/org.playframework.twirl.gradle.plugin/) into `settings.gradle.kts`: ```kotlin pluginManagement { diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index cbda535d..1265e85b 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -21,8 +21,7 @@ val compilerVersion: String = if (this.getProperty("twirl.compiler.version").isNullOrEmpty()) throw GradleException("`twirl.compiler.version` key didn't find in ${file.absolutePath}") }.getProperty("twirl.compiler.version") -// group = "org.playframework" // group and plugin id must use same top level namespace -group = "com.typesafe.play" // TODO: uncomment line above and remove this +group = "org.playframework.twirl" version = compilerVersion repositories { @@ -31,7 +30,7 @@ repositories { } dependencies { - compileOnly("com.typesafe.play:twirl-compiler_2.13:$compilerVersion") + compileOnly("org.playframework.twirl:twirl-compiler_2.13:$compilerVersion") testImplementation(libs.assertj) testImplementation(libs.commons.io) testImplementation(libs.freemarker) @@ -82,7 +81,7 @@ gradlePlugin { website.set("https://www.playframework.com/documentation/latest/ScalaTemplates") vcsUrl.set("https://github.com/playframework/twirl") val twirl by plugins.creating { - id = "com.typesafe.play.twirl" // TODO: rename to "org.playframework.twirl" + id = "org.playframework.twirl" displayName = "Twirl Plugin" description = "A Gradle plugin to compile Twirl templates" tags.set(listOf("playframework", "web", "template", "java", "scala")) diff --git a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties index 221e3a60..b446fa9a 100644 --- a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip -distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f +distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index ec873cff..23b8349f 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -76,7 +76,7 @@ private Configuration createDefaultTwirlConfiguration( .getDependencies() .create( String.format( - "com.typesafe.play:twirl-compiler_%s:%s", + "org.playframework.twirl:twirl-compiler_%s:%s", twirlExtension.getScalaVersion().get(), getDefaultTwirlVersion())); dependencies.add(twirlCompiler); }); diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java index d13826b2..50255064 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java @@ -100,12 +100,12 @@ protected BuildResult build(String gradleVersion, String... args) { static Stream gradleVersions() { // https://docs.gradle.org/current/userguide/scala_plugin.html#sec:configure_zinc_compiler if (getScalaVersion().equals("3")) { // Gradle 7.5+ - return Stream.of("7.6.2", "8.0.2", "8.3"); + return Stream.of("7.6.2", "8.0.2", "8.4"); } // https://docs.gradle.org/current/userguide/compatibility.html if (JavaVersion.current().compareTo(JavaVersion.VERSION_17) >= 0) { // Gradle 7.3+ - return Stream.of("7.6.2", "8.0.2", "8.3"); + return Stream.of("7.6.2", "8.0.2", "8.4"); } - return Stream.of("7.1.1", "7.6.2", "8.0.2", "8.3"); + return Stream.of("7.1.1", "7.6.2", "8.0.2", "8.4"); } } diff --git a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java index 6425fdc9..3d41c5ea 100644 --- a/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java +++ b/gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java @@ -25,7 +25,7 @@ class TwirlPluginTest { void init() { project = ProjectBuilder.builder().build(); project.getPluginManager().apply("application"); - project.getPluginManager().apply("com.typesafe.play.twirl"); + project.getPluginManager().apply("org.playframework.twirl"); } @Test @@ -47,7 +47,7 @@ void configurationShouldBeRegistered() { assertThat(conf.getDependencies()) .anyMatch( dependency -> - "com.typesafe.play".equals(dependency.getGroup()) + "org.playframework.twirl".equals(dependency.getGroup()) && dependency.getName().startsWith("twirl-compiler")); } diff --git a/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh b/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh index 8732cfe5..b7b1ffd8 100644 --- a/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh +++ b/gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh @@ -1,6 +1,6 @@ plugins { application - id("com.typesafe.play.twirl") + id("org.playframework.twirl") } repositories { @@ -9,7 +9,7 @@ repositories { } dependencies { - implementation("com.typesafe.play:twirl-api_${scalaVersion}:${twirlVersion}") + implementation("org.playframework.twirl:twirl-api_${scalaVersion}:${twirlVersion}") } twirl { From 76ad9622fef51a749f1a8995fc23a7d6e7aed849 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Tue, 10 Oct 2023 14:38:48 +0300 Subject: [PATCH 33/35] [Gradle] Sign only release artifacts --- gradle-twirl/build.gradle.kts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gradle-twirl/build.gradle.kts b/gradle-twirl/build.gradle.kts index 1265e85b..872a62d7 100644 --- a/gradle-twirl/build.gradle.kts +++ b/gradle-twirl/build.gradle.kts @@ -18,9 +18,15 @@ val compilerVersion: String = val file = file("$projectDir/../compiler/version.properties") if (!file.exists()) throw GradleException("Install Twirl Compiler to local Maven repository by `sbt +compiler/publishM2` command") file.inputStream().use { load(it) } - if (this.getProperty("twirl.compiler.version").isNullOrEmpty()) throw GradleException("`twirl.compiler.version` key didn't find in ${file.absolutePath}") + if (this.getProperty("twirl.compiler.version") + .isNullOrEmpty() + ) { + throw GradleException("`twirl.compiler.version` key didn't find in ${file.absolutePath}") + } }.getProperty("twirl.compiler.version") +val isRelease = !compilerVersion.endsWith("SNAPSHOT") + group = "org.playframework.twirl" version = compilerVersion @@ -63,9 +69,13 @@ testing { } signing { - val signingKey = Base64.getDecoder().decode(System.getenv("PGP_SECRET").orEmpty()).toString(UTF_8) - val signingPassword = System.getenv("PGP_PASSPHRASE").orEmpty() - useInMemoryPgpKeys(signingKey, signingPassword) + isRequired = isRelease + if (isRelease) { + val signingKey = + Base64.getDecoder().decode(System.getenv("PGP_SECRET").orEmpty()).toString(UTF_8) + val signingPassword = System.getenv("PGP_PASSPHRASE").orEmpty() + useInMemoryPgpKeys(signingKey, signingPassword) + } } nexusPublishing { @@ -89,7 +99,8 @@ gradlePlugin { } } -val headerLicense = "Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " +val headerLicense = + "Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " val headerLicenseHash = "# $headerLicense" val headerLicenseJava = "/*\n * $headerLicense\n */" From 90355c1b1cb6cc63a07a04b2426b0ac9a9e56ca8 Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Wed, 11 Oct 2023 11:17:07 +0300 Subject: [PATCH 34/35] [Gradle] `twirl` configuration shouldn't be consumable --- gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java index 23b8349f..60c5e869 100644 --- a/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java +++ b/gradle-twirl/src/main/java/play/twirl/gradle/TwirlPlugin.java @@ -69,6 +69,7 @@ private Configuration createDefaultTwirlConfiguration( conf.setDescription("The Twirl compiler library."); conf.setVisible(false); conf.setTransitive(true); + conf.setCanBeConsumed(false); conf.defaultDependencies( dependencies -> { Dependency twirlCompiler = From d1693013e15402a9c950a5b7b9ef8c5aa23a406d Mon Sep 17 00:00:00 2001 From: Sergey Morgunov Date: Thu, 12 Oct 2023 18:43:00 +0300 Subject: [PATCH 35/35] [Gradle] Use `all` gradle distribution instead of `bin` to source code Gradle API navigate --- gradle-twirl/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties index b446fa9a..d54311bf 100644 --- a/gradle-twirl/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-twirl/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionSha256Sum=f2b9ed0faf8472cbe469255ae6c86eddb77076c75191741b4a462f33128dd419 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists