From d3dedfe189eb6966e5f2c8d4b3681d675874f094 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sat, 4 Mar 2023 10:21:35 -0500 Subject: [PATCH 01/15] Updated to RIFE2 1.4.0 --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index df9f28e..9729b16 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,7 +27,7 @@ repositories { rife2 { mainClass.set("hello.App") - version.set("1.3.0") + version.set("1.4.0") useAgent.set(true) } From c8f47a935eaec8188255987a2954a91b8cadf129 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sat, 4 Mar 2023 10:22:04 -0500 Subject: [PATCH 02/15] Minor cleanups --- .../com/uwyn/rife2/gradle/Rife2Plugin.java | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 17bfd18..bb98cba 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -15,8 +15,7 @@ */ package com.uwyn.rife2.gradle; -import org.gradle.api.Plugin; -import org.gradle.api.Project; +import org.gradle.api.*; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; @@ -58,17 +57,21 @@ public class Rife2Plugin implements Plugin { var rife2CompilerClasspath = createRife2CompilerClasspathConfiguration(configurations, rife2Configuration); var rife2AgentClasspath = createRife2AgentConfiguration(configurations, dependencyHandler, rife2Extension); configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration); - var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath); - createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); - exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); + var precompileHtmlTemplates = registerPrecompileHtmlTemplateTask(project, rife2CompilerClasspath); + createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileHtmlTemplates); + exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileHtmlTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); registerRunTask(project, rife2Extension, rife2AgentClasspath); - TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); - bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates); + TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileHtmlTemplates); + bundlePrecompiledTemplatesIntoJarFile(tasks, precompileHtmlTemplates); configureMavenPublishing(project, plugins, configurations, uberJarTask); } - private static void configureMavenPublishing(Project project, PluginContainer plugins, ConfigurationContainer configurations, TaskProvider uberJarTask) { + @SuppressWarnings("unchecked") + private static void configureMavenPublishing(Project project, + PluginContainer plugins, + ConfigurationContainer configurations, + TaskProvider uberJarTask) { plugins.withId("maven-publish", unused -> { Configuration rife2UberJarElements = configurations.create("rife2UberJarElements", conf -> { conf.setDescription("Exposes the uber jar archive of the RIFE2 web application."); @@ -95,18 +98,22 @@ public class Rife2Plugin implements Plugin { }); } - private static void exposePrecompiledTemplatesToTestTask(Project project, ConfigurationContainer configurations, DependencyHandler dependencyHandler, TaskProvider precompileTemplates) { + private static void exposePrecompiledTemplatesToTestTask(Project project, + ConfigurationContainer configurations, + DependencyHandler dependencyHandler, + TaskProvider precompileTemplates) { configurations.getByName(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME) - .getDependencies() - .add(dependencyHandler.create(project.files(precompileTemplates))); + .getDependencies() + .add(dependencyHandler.create(project.files(precompileTemplates))); } - private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, TaskProvider precompileTemplates) { + private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, + TaskProvider precompileTemplates) { tasks.named("jar", Jar.class, jar -> jar.from(precompileTemplates)); } private void createRife2DevelopmentOnlyConfiguration(Project project, - ConfigurationContainer configurations, + ConfigurationContainer configurations, DependencyHandler dependencies, TaskProvider precompileTemplatesTask) { Configuration rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { @@ -135,16 +142,19 @@ public class Rife2Plugin implements Plugin { jar.from(precompileTemplatesTask); jar.into("webapp", spec -> spec.from(WEBAPP_SRCDIR)); jar.from(runtimeClasspath.getElements().map(e -> e.stream() - .filter(f -> f.getAsFile().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) - .map(project::zipTree) - .toList())); + .filter(f -> f.getAsFile().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) + .map(project::zipTree) + .toList())); plugins.withId("application", unused -> jar.manifest(manifest -> manifest.getAttributes().put("Main-Class", rife2Extension.getUberMainClass().get())) ); }); } - private static void configureAgent(Project project, PluginContainer plugins, Rife2Extension rife2Extension, Configuration rife2AgentClasspath) { + private static void configureAgent(Project project, + PluginContainer plugins, + Rife2Extension rife2Extension, + Configuration rife2AgentClasspath) { CommandLineArgumentProvider agentProvider = () -> { if (Boolean.TRUE.equals(rife2Extension.getUseAgent().get())) { return Collections.singleton("-javaagent:" + rife2AgentClasspath.getAsPath()); @@ -162,7 +172,8 @@ public class Rife2Plugin implements Plugin { return rife2; } - private static Configuration createRife2CompilerClasspathConfiguration(ConfigurationContainer configurations, Configuration rife2Configuration) { + private static Configuration createRife2CompilerClasspathConfiguration(ConfigurationContainer configurations, + Configuration rife2Configuration) { return configurations.create("rife2CompilerClasspath", conf -> { conf.setDescription("The RIFE2 compiler classpath"); conf.setCanBeConsumed(false); @@ -197,11 +208,11 @@ public class Rife2Plugin implements Plugin { return config; } - private static TaskProvider registerPrecompileTemplateTask(Project project, - Configuration rife2CompilerClasspath) { - return project.getTasks().register("precompileTemplates", PrecompileTemplates.class, task -> { + private static TaskProvider registerPrecompileHtmlTemplateTask(Project project, + Configuration rife2CompilerClasspath) { + return project.getTasks().register("precompileHtmlTemplates", PrecompileTemplates.class, task -> { task.setGroup(RIFE2_GROUP); - task.setDescription("Pre-compiles the templates."); + task.setDescription("Pre-compiles the HTML templates."); task.getVerbose().convention(true); task.getClasspath().from(rife2CompilerClasspath); task.getType().convention("html"); @@ -210,14 +221,15 @@ public class Rife2Plugin implements Plugin { }); } - private static void registerRunTask(Project project, Rife2Extension rife2Extension, - Configuration rife2CompilerClasspath) { + private static void registerRunTask(Project project, + Rife2Extension rife2Extension, + Configuration rife2CompilerClasspath) { project.getTasks().register("run", RunTask.class, task -> { task.setGroup(RIFE2_GROUP); task.setDescription("Runs this project as a web application."); task.getAgentClassPath().set(rife2CompilerClasspath.getAsPath()); task.getClasspath().from(project.getExtensions().getByType(SourceSetContainer.class) - .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()); + .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()); task.getMainClass().set(rife2Extension.getMainClass()); task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR)); }); From ed8046e83d8153f0bdbee8bbba3f0c0839aa0217 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sat, 4 Mar 2023 10:39:37 -0500 Subject: [PATCH 03/15] More minor cleanups --- .../java/com/uwyn/rife2/gradle/Rife2Plugin.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index bb98cba..6160749 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -62,7 +62,7 @@ public class Rife2Plugin implements Plugin { exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileHtmlTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); registerRunTask(project, rife2Extension, rife2AgentClasspath); - TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileHtmlTemplates); + var uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileHtmlTemplates); bundlePrecompiledTemplatesIntoJarFile(tasks, precompileHtmlTemplates); configureMavenPublishing(project, plugins, configurations, uberJarTask); } @@ -73,12 +73,13 @@ public class Rife2Plugin implements Plugin { ConfigurationContainer configurations, TaskProvider uberJarTask) { plugins.withId("maven-publish", unused -> { - Configuration rife2UberJarElements = configurations.create("rife2UberJarElements", conf -> { + var rife2UberJarElements = configurations.create("rife2UberJarElements", conf -> { conf.setDescription("Exposes the uber jar archive of the RIFE2 web application."); conf.setCanBeResolved(false); conf.setCanBeConsumed(true); conf.getOutgoing().artifact(uberJarTask, artifact -> artifact.setClassifier("uber")); - AttributeContainer runtimeAttributes = configurations.getByName(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME).getAttributes(); + + var runtimeAttributes = configurations.getByName(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME).getAttributes(); conf.attributes(attrs -> { for (Attribute attribute : runtimeAttributes.keySet()) { Object value = runtimeAttributes.getAttribute(attribute); @@ -93,7 +94,8 @@ public class Rife2Plugin implements Plugin { } }); }); - AdhocComponentWithVariants component = (AdhocComponentWithVariants) project.getComponents().getByName("java"); + + var component = (AdhocComponentWithVariants) project.getComponents().getByName("java"); component.addVariantsFromConfiguration(rife2UberJarElements, ConfigurationVariantDetails::mapToOptional); }); } @@ -116,7 +118,7 @@ public class Rife2Plugin implements Plugin { ConfigurationContainer configurations, DependencyHandler dependencies, TaskProvider precompileTemplatesTask) { - Configuration rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { + var rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { conf.setDescription("Dependencies which should only be visible when running the application in development mode (and not in tests)."); conf.setCanBeConsumed(false); conf.setCanBeResolved(false); @@ -137,10 +139,10 @@ public class Rife2Plugin implements Plugin { var base = project.getExtensions().getByType(BasePluginExtension.class); jar.getArchiveBaseName().convention(project.provider(() -> base.getArchivesName().get() + "-uber")); jar.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE); - var runtimeClasspath = project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); jar.from(javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput()); jar.from(precompileTemplatesTask); jar.into("webapp", spec -> spec.from(WEBAPP_SRCDIR)); + var runtimeClasspath = project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); jar.from(runtimeClasspath.getElements().map(e -> e.stream() .filter(f -> f.getAsFile().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) .map(project::zipTree) From aeaadfb1ccc5e0bf6e4cf3c0b07db32667c4fb90 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sat, 4 Mar 2023 11:53:59 -0500 Subject: [PATCH 04/15] Added support for configurable precompiled template types --- app/build.gradle.kts | 2 + .../rife2/gradle/PrecompileTemplates.java | 37 ++++++++++--------- .../com/uwyn/rife2/gradle/Rife2Extension.java | 3 ++ .../com/uwyn/rife2/gradle/Rife2Plugin.java | 34 +++++++++-------- .../com/uwyn/rife2/gradle/TemplateType.java | 22 +++++++++++ 5 files changed, 66 insertions(+), 32 deletions(-) create mode 100644 build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9729b16..de722df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,6 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent +import com.uwyn.rife2.gradle.TemplateType.* plugins { id("com.uwyn.rife2") @@ -29,6 +30,7 @@ rife2 { mainClass.set("hello.App") version.set("1.4.0") useAgent.set(true) + precompiledTemplateTypes.addAll(HTML) } dependencies { diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java index 871df2e..8b9ab9a 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java @@ -18,6 +18,7 @@ package com.uwyn.rife2.gradle; import org.gradle.api.DefaultTask; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Classpath; @@ -45,7 +46,7 @@ public abstract class PrecompileTemplates extends DefaultTask { public abstract DirectoryProperty getTemplatesDirectory(); @Input - public abstract Property getType(); + public abstract ListProperty getTypes(); @Input @Optional @@ -63,21 +64,23 @@ public abstract class PrecompileTemplates extends DefaultTask { @TaskAction public void precompileTemplates() { - getExecOperations().javaexec(javaexec -> { - javaexec.setClasspath(getClasspath()); - javaexec.getMainClass().set("rife.template.TemplateDeployer"); - List args = new ArrayList<>(); - if (getVerbose().isPresent() && Boolean.TRUE.equals(getVerbose().get())) { - args.add("-verbose"); - } - args.add("-t"); - args.add(getType().get()); - args.add("-d"); - args.add(getOutputDirectory().get().getAsFile().getPath()); - args.add("-encoding"); - args.add(getEncoding().orElse("UTF-8").get()); - args.add(getTemplatesDirectory().get().getAsFile().getPath()); - javaexec.args(args); - }); + for (var type : getTypes().get()) { + getExecOperations().javaexec(javaexec -> { + javaexec.setClasspath(getClasspath()); + javaexec.getMainClass().set("rife.template.TemplateDeployer"); + List args = new ArrayList<>(); + if (getVerbose().isPresent() && Boolean.TRUE.equals(getVerbose().get())) { + args.add("-verbose"); + } + args.add("-t"); + args.add(type.identifier()); + args.add("-d"); + args.add(getOutputDirectory().get().getAsFile().getPath()); + args.add("-encoding"); + args.add(getEncoding().orElse("UTF-8").get()); + args.add(getTemplatesDirectory().get().getAsFile().getPath()); + javaexec.args(args); + }); + } } } diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java index ebd97e0..23a4ece 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java @@ -15,6 +15,7 @@ */ package com.uwyn.rife2.gradle; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; @SuppressWarnings("unused") @@ -26,4 +27,6 @@ public abstract class Rife2Extension { public abstract Property getUseAgent(); public abstract Property getUberMainClass(); + + public abstract ListProperty getPrecompiledTemplateTypes(); } diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 6160749..9409eea 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -20,7 +20,6 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.attributes.Attribute; -import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.attributes.Bundling; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.ConfigurationVariantDetails; @@ -53,17 +52,21 @@ public class Rife2Plugin implements Plugin { var configurations = project.getConfigurations(); var dependencyHandler = project.getDependencies(); var tasks = project.getTasks(); + var rife2Configuration = createRife2Configuration(configurations, dependencyHandler, rife2Extension); var rife2CompilerClasspath = createRife2CompilerClasspathConfiguration(configurations, rife2Configuration); var rife2AgentClasspath = createRife2AgentConfiguration(configurations, dependencyHandler, rife2Extension); configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration); - var precompileHtmlTemplates = registerPrecompileHtmlTemplateTask(project, rife2CompilerClasspath); - createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileHtmlTemplates); - exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileHtmlTemplates); + + var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath, rife2Extension); + createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); + exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); registerRunTask(project, rife2Extension, rife2AgentClasspath); - var uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileHtmlTemplates); - bundlePrecompiledTemplatesIntoJarFile(tasks, precompileHtmlTemplates); + var uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); + + bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates); + configureMavenPublishing(project, plugins, configurations, uberJarTask); } @@ -103,15 +106,15 @@ public class Rife2Plugin implements Plugin { private static void exposePrecompiledTemplatesToTestTask(Project project, ConfigurationContainer configurations, DependencyHandler dependencyHandler, - TaskProvider precompileTemplates) { + TaskProvider precompileTemplatesTask) { configurations.getByName(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME) .getDependencies() - .add(dependencyHandler.create(project.files(precompileTemplates))); + .add(dependencyHandler.create(project.files(precompileTemplatesTask))); } private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, - TaskProvider precompileTemplates) { - tasks.named("jar", Jar.class, jar -> jar.from(precompileTemplates)); + TaskProvider precompileTemplatesTask) { + tasks.named("jar", Jar.class, jar -> jar.from(precompileTemplatesTask)); } private void createRife2DevelopmentOnlyConfiguration(Project project, @@ -210,14 +213,15 @@ public class Rife2Plugin implements Plugin { return config; } - private static TaskProvider registerPrecompileHtmlTemplateTask(Project project, - Configuration rife2CompilerClasspath) { - return project.getTasks().register("precompileHtmlTemplates", PrecompileTemplates.class, task -> { + private static TaskProvider registerPrecompileTemplateTask(Project project, + Configuration rife2CompilerClasspath, + Rife2Extension rife2Extension) { + return project.getTasks().register("precompileTemplates", PrecompileTemplates.class, task -> { task.setGroup(RIFE2_GROUP); - task.setDescription("Pre-compiles the HTML templates."); + task.setDescription("Pre-compiles the templates."); task.getVerbose().convention(true); task.getClasspath().from(rife2CompilerClasspath); - task.getType().convention("html"); + task.getTypes().convention(rife2Extension.getPrecompiledTemplateTypes()); task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR)); task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(DEFAULT_GENERATED_RIFE2_CLASSES_DIR)); }); diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java new file mode 100644 index 0000000..0e2a30b --- /dev/null +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java @@ -0,0 +1,22 @@ +package com.uwyn.rife2.gradle; + +import java.io.Serializable; + +public class TemplateType implements Serializable { + public static TemplateType HTML = new TemplateType("html"); + public static TemplateType JSON = new TemplateType("json"); + public static TemplateType SVG = new TemplateType("svg"); + public static TemplateType XML = new TemplateType("xml"); + public static TemplateType TXT = new TemplateType("txt"); + public static TemplateType SQL = new TemplateType("sql"); + + private final String identifier_; + + public TemplateType(String identifier) { + identifier_ = identifier; + } + + public String identifier() { + return identifier_; + } +} \ No newline at end of file From 86b7ec21d9de24b7a5e0f96ed47aa7aead186a5f Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sat, 4 Mar 2023 12:09:59 -0500 Subject: [PATCH 05/15] Added serialVersionUID to TemplateType --- .../src/main/java/com/uwyn/rife2/gradle/TemplateType.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java index 0e2a30b..6fff9a7 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java @@ -1,8 +1,11 @@ package com.uwyn.rife2.gradle; +import java.io.Serial; import java.io.Serializable; public class TemplateType implements Serializable { + @Serial private static final long serialVersionUID = -2736320275307140837L; + public static TemplateType HTML = new TemplateType("html"); public static TemplateType JSON = new TemplateType("json"); public static TemplateType SVG = new TemplateType("svg"); From 65e579966cd2f72813a661d356f7af4209dbe6cc Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 11:22:28 +0100 Subject: [PATCH 06/15] Revert `RunTask` --- app/build.gradle.kts | 11 ++-- app/src/main/templates/hello.html | 2 +- build-logic/build.gradle.kts | 7 --- .../com/uwyn/rife2/gradle/Rife2Extension.java | 3 -- .../com/uwyn/rife2/gradle/Rife2Plugin.java | 40 +++++--------- .../java/com/uwyn/rife2/gradle/RunTask.java | 54 ------------------- war/build.gradle.kts | 5 +- 7 files changed, 23 insertions(+), 99 deletions(-) delete mode 100644 build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index de722df..07daedc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,15 +3,19 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent import com.uwyn.rife2.gradle.TemplateType.* plugins { + application id("com.uwyn.rife2") `maven-publish` } -version = 1.0 -group = "com.example" - base { archivesName.set("hello") + version = 1.0 + group = "com.example" +} + +application { + mainClass.set("hello.App") } java { @@ -27,7 +31,6 @@ repositories { } rife2 { - mainClass.set("hello.App") version.set("1.4.0") useAgent.set(true) precompiledTemplateTypes.addAll(HTML) diff --git a/app/src/main/templates/hello.html b/app/src/main/templates/hello.html index f7ffe8b..59ff81b 100644 --- a/app/src/main/templates/hello.html +++ b/app/src/main/templates/hello.html @@ -8,4 +8,4 @@

Hello World

- \ No newline at end of file + diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b9ea647..b29e98f 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -10,13 +10,6 @@ dependencies { gradleApi() } -tasks { - withType { - options.isDeprecation = true - options.compilerArgs.add("-Xlint:unchecked") - } -} - gradlePlugin { plugins { create("rife2") { diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java index 23a4ece..30fa218 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java @@ -18,10 +18,7 @@ package com.uwyn.rife2.gradle; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; -@SuppressWarnings("unused") public abstract class Rife2Extension { - public abstract Property getMainClass(); - public abstract Property getVersion(); public abstract Property getUseAgent(); diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 9409eea..9a1b5fe 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -25,10 +25,14 @@ import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.ConfigurationVariantDetails; import org.gradle.api.file.DuplicatesStrategy; import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.PluginContainer; -import org.gradle.api.tasks.*; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.testing.Test; import org.gradle.process.CommandLineArgumentProvider; @@ -36,7 +40,6 @@ import org.gradle.process.CommandLineArgumentProvider; import java.util.Collections; import java.util.Locale; -@SuppressWarnings({"ALL", "unused"}) public class Rife2Plugin implements Plugin { public static final String DEFAULT_TEMPLATES_DIR = "src/main/templates"; public static final String DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2"; @@ -62,9 +65,7 @@ public class Rife2Plugin implements Plugin { createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); - registerRunTask(project, rife2Extension, rife2AgentClasspath); - var uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); - + TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates); configureMavenPublishing(project, plugins, configurations, uberJarTask); @@ -86,13 +87,11 @@ public class Rife2Plugin implements Plugin { conf.attributes(attrs -> { for (Attribute attribute : runtimeAttributes.keySet()) { Object value = runtimeAttributes.getAttribute(attribute); - if (value != null) { - if (Bundling.class.equals(attribute.getType())) { - attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED)); - } else { - //noinspection unchecked - attrs.attribute((Attribute) attribute, value); - } + //noinspection unchecked + if (Bundling.class.equals(attribute.getType())) { + attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED)); + } else { + attrs.attribute((Attribute) attribute, value); } } }); @@ -173,7 +172,8 @@ public class Rife2Plugin implements Plugin { private static Rife2Extension createRife2Extension(Project project) { var rife2 = project.getExtensions().create("rife2", Rife2Extension.class); rife2.getUseAgent().convention(false); - rife2.getUberMainClass().set(rife2.getMainClass() + "Uber"); + rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() + .map(mainClass -> mainClass + "Uber")); return rife2; } @@ -226,18 +226,4 @@ public class Rife2Plugin implements Plugin { task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(DEFAULT_GENERATED_RIFE2_CLASSES_DIR)); }); } - - private static void registerRunTask(Project project, - Rife2Extension rife2Extension, - Configuration rife2CompilerClasspath) { - project.getTasks().register("run", RunTask.class, task -> { - task.setGroup(RIFE2_GROUP); - task.setDescription("Runs this project as a web application."); - task.getAgentClassPath().set(rife2CompilerClasspath.getAsPath()); - task.getClasspath().from(project.getExtensions().getByType(SourceSetContainer.class) - .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()); - task.getMainClass().set(rife2Extension.getMainClass()); - task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR)); - }); - } } diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java deleted file mode 100644 index c107544..0000000 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2003-2021 the original author or 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. - */ -package com.uwyn.rife2.gradle; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.*; -import org.gradle.process.ExecOperations; - -import javax.inject.Inject; -import java.util.List; - -@CacheableTask -public abstract class RunTask extends DefaultTask { - @Input - public abstract Property getAgentClassPath(); - - @Classpath - public abstract ConfigurableFileCollection getClasspath(); - - @Inject - protected abstract ExecOperations getExecOperations(); - - @Input - public abstract Property getMainClass(); - - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - public abstract DirectoryProperty getTemplatesDirectory(); - - @TaskAction - public void run() { - getExecOperations().javaexec(run -> { - run.setClasspath(getProject().getObjects().fileCollection().from(getTemplatesDirectory()).plus(getClasspath())); - run.getMainClass().set(getMainClass()); - run.args(List.of("-javaagent:" + getAgentClassPath().get())); - }); - } -} \ No newline at end of file diff --git a/war/build.gradle.kts b/war/build.gradle.kts index 477784d..206f003 100644 --- a/war/build.gradle.kts +++ b/war/build.gradle.kts @@ -2,10 +2,9 @@ plugins { war } -version = 1.0 - base { archivesName.set("hello") + version = 1.0 } repositories { @@ -21,4 +20,4 @@ tasks.war { webAppDirectory.set(file("../app/src/main/webapp")) webXml = file("src/web.xml") rootSpec.exclude("**/jetty*.jar", "**/slf4j*.jar", "**/rife2*-agent.jar") -} +} \ No newline at end of file From c9f286132c8dd78bac12ad55fd155a7092f261ea Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 13:38:42 +0100 Subject: [PATCH 07/15] Fix reloading of templates This commit fixes how templates were reloaded. There was a bug in the plugin which used the output of the precompiled templates for development only dependencies, instead of the templates directory. This caused the templates to be always compiled and added as a resource on runtime classpath, when we only wanted the raw templates. This commit also adds functional tests to the build logic, which can be executed by running `./gradlew build-logic:test`. --- app/build.gradle.kts | 10 +- build-logic/build.gradle.kts | 11 ++ build-logic/settings.gradle.kts | 8 + .../com/uwyn/rife2/gradle/Rife2Plugin.java | 11 +- .../src/test-projects/minimal/build.gradle | 40 ++++ .../src/test-projects/minimal/settings.gradle | 1 + .../minimal/src/main/java/hello/App.java | 16 ++ .../minimal/src/main/java/hello/AppUber.java | 11 ++ .../META-INF/native-image/reflect-config.json | 6 + .../native-image/resource-config.json | 8 + .../minimal/src/main/templates/hello.html | 11 ++ .../minimal/src/main/webapp/css/style.css | 21 ++ .../minimal/src/test/java/hello/AppTest.java | 24 +++ .../gradle/AbstractFunctionalTest.groovy | 184 ++++++++++++++++++ .../uwyn/rife2/gradle/PackagingTest.groovy | 35 ++++ .../com/uwyn/rife2/gradle/TeeWriter.groovy | 81 ++++++++ .../gradle/TemplateCompilationTest.groovy | 58 ++++++ gradle/libs.versions.toml | 17 ++ 18 files changed, 542 insertions(+), 11 deletions(-) create mode 100644 build-logic/src/test-projects/minimal/build.gradle create mode 100644 build-logic/src/test-projects/minimal/settings.gradle create mode 100644 build-logic/src/test-projects/minimal/src/main/java/hello/App.java create mode 100644 build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java create mode 100644 build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json create mode 100644 build-logic/src/test-projects/minimal/src/main/templates/hello.html create mode 100644 build-logic/src/test-projects/minimal/src/main/webapp/css/style.css create mode 100644 build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07daedc..29c1b77 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,16 +33,14 @@ repositories { rife2 { version.set("1.4.0") useAgent.set(true) - precompiledTemplateTypes.addAll(HTML) } dependencies { - runtimeOnly("org.eclipse.jetty:jetty-server:11.0.13") - runtimeOnly("org.eclipse.jetty:jetty-servlet:11.0.13") - runtimeOnly("org.slf4j:slf4j-simple:2.0.5") + runtimeOnly(libs.bundles.jetty) + runtimeOnly(libs.slf4j.simple) - testImplementation("org.jsoup:jsoup:1.15.3") - testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") + testImplementation(libs.jsoup) + testImplementation(libs.junit.jupiter) } tasks { diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b29e98f..c7fabff 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,5 +1,6 @@ plugins { `java-gradle-plugin` + groovy } repositories { @@ -8,6 +9,8 @@ repositories { dependencies { gradleApi() + testImplementation(libs.spock.core) + testImplementation(gradleTestKit()) } gradlePlugin { @@ -18,3 +21,11 @@ gradlePlugin { } } } + +tasks.withType().configureEach { + useJUnitPlatform() + testLogging { + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + events = setOf(org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED, org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED, org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED) + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 7fbbd44..9ac59bd 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1 +1,9 @@ rootProject.name = "build-logic" + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 9a1b5fe..2777742 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -45,6 +45,7 @@ public class Rife2Plugin implements Plugin { public static final String DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2"; public static final String RIFE2_GROUP = "rife2"; public static final String WEBAPP_SRCDIR = "src/main/webapp"; + public static final String PRECOMPILE_TEMPLATES_TASK_NAME = "precompileTemplates"; @Override public void apply(Project project) { @@ -62,7 +63,7 @@ public class Rife2Plugin implements Plugin { configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration); var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath, rife2Extension); - createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); + createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); @@ -118,14 +119,13 @@ public class Rife2Plugin implements Plugin { private void createRife2DevelopmentOnlyConfiguration(Project project, ConfigurationContainer configurations, - DependencyHandler dependencies, - TaskProvider precompileTemplatesTask) { + DependencyHandler dependencies) { var rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { conf.setDescription("Dependencies which should only be visible when running the application in development mode (and not in tests)."); conf.setCanBeConsumed(false); conf.setCanBeResolved(false); }); - rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(precompileTemplatesTask))); + rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(DEFAULT_TEMPLATES_DIR))); configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly); } @@ -174,6 +174,7 @@ public class Rife2Plugin implements Plugin { rife2.getUseAgent().convention(false); rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() .map(mainClass -> mainClass + "Uber")); + rife2.getPrecompiledTemplateTypes().convention(Collections.singletonList(TemplateType.HTML)); return rife2; } @@ -216,7 +217,7 @@ public class Rife2Plugin implements Plugin { private static TaskProvider registerPrecompileTemplateTask(Project project, Configuration rife2CompilerClasspath, Rife2Extension rife2Extension) { - return project.getTasks().register("precompileTemplates", PrecompileTemplates.class, task -> { + return project.getTasks().register(PRECOMPILE_TEMPLATES_TASK_NAME, PrecompileTemplates.class, task -> { task.setGroup(RIFE2_GROUP); task.setDescription("Pre-compiles the templates."); task.getVerbose().convention(true); diff --git a/build-logic/src/test-projects/minimal/build.gradle b/build-logic/src/test-projects/minimal/build.gradle new file mode 100644 index 0000000..697f9f5 --- /dev/null +++ b/build-logic/src/test-projects/minimal/build.gradle @@ -0,0 +1,40 @@ +import com.uwyn.rife2.gradle.TemplateType.* + +plugins { + id("application") + id("com.uwyn.rife2") +} + +base { + archivesName = "hello" + version = 1.0 + group = "com.example" +} + +application { + mainClass = "hello.App" +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +rife2 { + version = "1.4.0" + useAgent = true +} + +dependencies { + runtimeOnly("org.eclipse.jetty:jetty-server:11.0.13") + runtimeOnly("org.eclipse.jetty:jetty-servlet:11.0.13") + runtimeOnly("org.slf4j:slf4j-simple:2.0.5") + + testImplementation("org.jsoup:jsoup:1.15.3") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") +} diff --git a/build-logic/src/test-projects/minimal/settings.gradle b/build-logic/src/test-projects/minimal/settings.gradle new file mode 100644 index 0000000..e87ab3c --- /dev/null +++ b/build-logic/src/test-projects/minimal/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'minimal' diff --git a/build-logic/src/test-projects/minimal/src/main/java/hello/App.java b/build-logic/src/test-projects/minimal/src/main/java/hello/App.java new file mode 100644 index 0000000..43a1870 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/java/hello/App.java @@ -0,0 +1,16 @@ +package hello; + +import rife.engine.*; + +public class App extends Site { + public void setup() { + var hello = get("/hello", c -> c.print(c.template("hello"))); + get("/", c -> c.redirect(hello)); + } + + public static void main(String[] args) { + new Server() + .staticResourceBase("src/main/webapp") + .start(new App()); + } +} diff --git a/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java b/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java new file mode 100644 index 0000000..8dbf6a6 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java @@ -0,0 +1,11 @@ +package hello; + +import rife.engine.Server; + +public class AppUber extends App { + public static void main(String[] args) { + new Server() + .staticUberJarResourceBase("webapp") + .start(new AppUber()); + } +} \ No newline at end of file diff --git a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..9b0c3be --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,6 @@ +[ +{ + "name":"rife.template.html.hello", + "methods":[{"name":"","parameterTypes":[] }] +} +] diff --git a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 0000000..ad0b0a3 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,8 @@ +{ + "resources":{ + "includes":[ + {"pattern":"^webapp/.*$"} + ] + }, + "bundles":[] +} diff --git a/build-logic/src/test-projects/minimal/src/main/templates/hello.html b/build-logic/src/test-projects/minimal/src/main/templates/hello.html new file mode 100644 index 0000000..59ff81b --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/templates/hello.html @@ -0,0 +1,11 @@ + + + + + <!--v title-->Hello<!--/v--> + + + +

Hello World

+ + diff --git a/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css b/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css new file mode 100644 index 0000000..52bf6c7 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css @@ -0,0 +1,21 @@ +:root { + /* fonts */ + --main-font: sans-serif; + + /* font sizes */ + --main-font-size: 18px; + + /* colors */ + --main-background-color: #0d0d0d; + --main-text-color: #d0d0d0; + + /* margins and padding */ + --content-padding: 2em; +} +body { + background: var(--main-background-color); + font-family: var(--main-font); + font-style: var(--main-font-size); + color: var(--main-text-color); + padding: var(--content-padding); +} \ No newline at end of file diff --git a/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java b/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java new file mode 100644 index 0000000..45a0763 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java @@ -0,0 +1,24 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package hello; + +import org.junit.jupiter.api.Test; +import rife.test.MockConversation; + +import static org.junit.jupiter.api.Assertions.*; + +public class AppTest { + @Test + void verifyRoot() { + var m = new MockConversation(new App()); + assertEquals(m.doRequest("/").getStatus(), 302); + } + + @Test + void verifyHello() { + var m = new MockConversation(new App()); + assertEquals("Hello", m.doRequest("/hello") + .getTemplate().getValue("title")); + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy new file mode 100644 index 0000000..7629d3f --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy @@ -0,0 +1,184 @@ +package com.uwyn.rife2.gradle + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.gradle.util.GFileUtils +import org.gradle.util.GradleVersion +import spock.lang.Specification +import spock.lang.TempDir + +import java.nio.file.Path + +abstract class AbstractFunctionalTest extends Specification { + + private final String gradleVersion = System.getProperty("gradleVersion", GradleVersion.current().version) + + @TempDir + Path testDirectory + + boolean debug + + private StringWriter outputWriter + private StringWriter errorOutputWriter + private String output + private String errorOutput + + BuildResult result + + Path path(String... pathElements) { + Path cur = testDirectory + pathElements.each { + cur = cur.resolve(it) + } + cur + } + + File file(String... pathElements) { + path(pathElements).toFile() + } + + File getGroovyBuildFile() { + file("build.gradle") + } + + File getBuildFile() { + groovyBuildFile + } + + File getKotlinBuildFile() { + file("build.gradle.kts") + } + + File getGroovySettingsFile() { + file("settings.gradle") + } + + File getKotlinSettingsFile() { + file("settings.gradle.kts") + } + + File getSettingsFile() { + groovySettingsFile + } + + void run(String... args) { + try { + result = newRunner(args) + .build() + } finally { + recordOutputs() + } + } + + void outputContains(String text) { + assert output.normalize().contains(text.normalize()) + } + + void outputDoesNotContain(String text) { + assert !output.normalize().contains(text.normalize()) + } + + void errorOutputContains(String text) { + assert errorOutput.normalize().contains(text.normalize()) + } + + void tasks(@DelegatesTo(value = TaskExecutionGraph, strategy = Closure.DELEGATE_FIRST) Closure spec) { + def graph = new TaskExecutionGraph() + spec.delegate = graph + spec.resolveStrategy = Closure.DELEGATE_FIRST + spec() + } + + private void recordOutputs() { + output = outputWriter.toString() + errorOutput = errorOutputWriter.toString() + } + + private GradleRunner newRunner(String... args) { + outputWriter = new StringWriter() + errorOutputWriter = new StringWriter() + ArrayList autoArgs = computeAutoArgs() + def runner = GradleRunner.create() + .forwardStdOutput(tee(new OutputStreamWriter(System.out), outputWriter)) + .forwardStdError(tee(new OutputStreamWriter(System.err), errorOutputWriter)) + .withPluginClasspath() + .withProjectDir(testDirectory.toFile()) + .withArguments([*autoArgs, *args]) + if (gradleVersion) { + runner.withGradleVersion(gradleVersion) + } + if (debug) { + runner.withDebug(true) + } + runner + } + + private ArrayList computeAutoArgs() { + List autoArgs = [ + "-s", + "--console=verbose" + ] + if (Boolean.getBoolean("config.cache")) { + autoArgs << '--configuration-cache' + } + autoArgs + } + + private static Writer tee(Writer one, Writer two) { + return TeeWriter.of(one, two) + } + + void fails(String... args) { + try { + result = newRunner(args) + .buildAndFail() + } finally { + recordOutputs() + } + } + + private class TaskExecutionGraph { + void succeeded(String... tasks) { + tasks.each { task -> + contains(task) + assert result.task(task).outcome == TaskOutcome.SUCCESS + } + } + + void failed(String... tasks) { + tasks.each { task -> + contains(task) + assert result.task(task).outcome == TaskOutcome.FAILED + } + } + + void skipped(String... tasks) { + tasks.each { task -> + contains(task) + assert result.task(task).outcome == TaskOutcome.SKIPPED + } + } + + void contains(String... tasks) { + tasks.each { task -> + assert result.task(task) != null: "Expected to find task $task in the graph but it was missing" + } + } + + void doesNotContain(String... tasks) { + tasks.each { task -> + assert result.task(task) == null: "Task $task should be missing from the task graph but it was found with an outcome of ${result.task(task).outcome}" + } + } + } + + void usesProject(String name) { + File sampleDir = new File("src/test-projects/$name") + GFileUtils.copyDirectory(sampleDir, testDirectory.toFile()) + } + + File file(String path) { + new File(testDirectory.toFile(), path) + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy new file mode 100644 index 0000000..e08ff3c --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy @@ -0,0 +1,35 @@ +package com.uwyn.rife2.gradle + +import java.nio.file.FileSystems +import java.nio.file.Files + +class PackagingTest extends AbstractFunctionalTest { + def setup() { + usesProject("minimal") + } + + def "#archive contains compiled resources"() { + def jarFile = file(archive).toPath() + when: + run task + + then: "compiles templates are found in the archive" + tasks { + succeeded ":${Rife2Plugin.PRECOMPILE_TEMPLATES_TASK_NAME}" + } + Files.exists(jarFile) + try (def fs = FileSystems.newFileSystem(jarFile, [:])) { + fs.getRootDirectories().each { + Files.walk(it).forEach { path -> + println path + } + } + assert Files.exists(fs.getPath("/rife/template/html/hello.class")) + } + + where: + task | archive + 'jar' | 'build/libs/hello-1.0.jar' + 'uberJar' | 'build/libs/hello-uber-1.0.jar' + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy new file mode 100644 index 0000000..89f60c1 --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy @@ -0,0 +1,81 @@ +package com.uwyn.rife2.gradle + +import groovy.transform.CompileStatic + +@CompileStatic +class TeeWriter extends Writer { + private final Writer one + private final Writer two + + static TeeWriter of(Writer one, Writer two) { + new TeeWriter(one, two) + } + + private TeeWriter(Writer one, Writer two) { + this.one = one + this.two = two + } + + @Override + void write(int c) throws IOException { + try { + one.write(c) + } finally { + two.write(c) + } + } + + @Override + void write(char[] cbuf) throws IOException { + try { + one.write(cbuf) + } finally { + two.write(cbuf) + } + } + + @Override + void write(char[] cbuf, int off, int len) throws IOException { + try { + one.write(cbuf, off, len) + } finally { + two.write(cbuf, off, len) + } + } + + @Override + void write(String str) throws IOException { + try { + one.write(str) + } finally { + two.write(str) + } + } + + @Override + void write(String str, int off, int len) throws IOException { + try { + one.write(str, off, len) + } finally { + two.write(str, off, len) + } + } + + @Override + void flush() throws IOException { + try { + one.flush() + } finally { + two.flush() + } + } + + @Override + void close() throws IOException { + try { + one.close() + } finally { + two.close() + } + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy new file mode 100644 index 0000000..12f14a6 --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy @@ -0,0 +1,58 @@ +package com.uwyn.rife2.gradle + +class TemplateCompilationTest extends AbstractFunctionalTest { + def setup() { + usesProject("minimal") + } + + def "doesn't precompile templates when calling `run`"() { + given: + buildFile << """ + tasks.named("run") { + doFirst { + throw new RuntimeException("force stop") + } + } + """ + when: + fails 'run' + + then: "precompile templates task must not be present in task graph" + errorOutputContains("force stop") + tasks { + doesNotContain ":${Rife2Plugin.PRECOMPILE_TEMPLATES_TASK_NAME}" + } + } + + def "`run` task classpath includes template sources"() { + given: + buildFile << """ + tasks.register("dumpRunClasspath") { + doLast { + tasks.named("run").get().classpath.files.each { + println "Classpath entry: \$it" + } + } + } + """ + + when: + run("dumpRunClasspath") + + then: "template sources must be present in the classpath" + outputContains("Classpath entry: ${file("src/main/templates").absolutePath}") + } + + def "compiles templates when running #task"() { + when: + run task + + then: "precompile templates task must be present in task graph" + tasks { + succeeded ":${Rife2Plugin.PRECOMPILE_TEMPLATES_TASK_NAME}" + } + + where: + task << ['jar', 'test', 'uberJar'] + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..3a8c200 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +[versions] +jetty = "11.0.13" +jsoup = "1.15.3" +junit-jupiter = "5.9.1" +slf4j = "2.0.5" +spock = "2.3-groovy-3.0" + +[libraries] +jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "jetty" } +jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "jetty" } +jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } + +[bundles] +jetty = [ "jetty-server", "jetty-servlet" ] From 5ca1fa305bb338b86e308561df7dbbc40606075e Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sun, 5 Mar 2023 08:51:14 -0500 Subject: [PATCH 08/15] Gradle plugin test fix --- .../com/uwyn/rife2/gradle/TemplateCompilationTest.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy index 12f14a6..9d2571e 100644 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy @@ -29,8 +29,9 @@ class TemplateCompilationTest extends AbstractFunctionalTest { buildFile << """ tasks.register("dumpRunClasspath") { doLast { + def rootPath = rootProject.projectDir.toPath() tasks.named("run").get().classpath.files.each { - println "Classpath entry: \$it" + println "Classpath entry: \${rootPath.relativize(it.toPath())}" } } } @@ -40,7 +41,7 @@ class TemplateCompilationTest extends AbstractFunctionalTest { run("dumpRunClasspath") then: "template sources must be present in the classpath" - outputContains("Classpath entry: ${file("src/main/templates").absolutePath}") + outputContains("Classpath entry: src/main/templates") } def "compiles templates when running #task"() { From cf4870745c35aab5c3fea3c5cff11a0d251340b5 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sun, 5 Mar 2023 09:07:29 -0500 Subject: [PATCH 09/15] Made precompiledTemplateTypes plugin option not use HTML by default Cleanup minimal test project. --- app/build.gradle.kts | 1 + .../main/java/com/uwyn/rife2/gradle/Rife2Plugin.java | 1 - build-logic/src/test-projects/minimal/build.gradle | 3 ++- .../minimal/src/main/java/hello/AppUber.java | 11 ----------- .../META-INF/native-image/reflect-config.json | 6 ------ .../META-INF/native-image/resource-config.json | 8 -------- 6 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java delete mode 100644 build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json delete mode 100644 build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 29c1b77..656dcab 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,6 +33,7 @@ repositories { rife2 { version.set("1.4.0") useAgent.set(true) + precompiledTemplateTypes.add(HTML) } dependencies { diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 2777742..f4a6cda 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -174,7 +174,6 @@ public class Rife2Plugin implements Plugin { rife2.getUseAgent().convention(false); rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() .map(mainClass -> mainClass + "Uber")); - rife2.getPrecompiledTemplateTypes().convention(Collections.singletonList(TemplateType.HTML)); return rife2; } diff --git a/build-logic/src/test-projects/minimal/build.gradle b/build-logic/src/test-projects/minimal/build.gradle index 697f9f5..5bc074f 100644 --- a/build-logic/src/test-projects/minimal/build.gradle +++ b/build-logic/src/test-projects/minimal/build.gradle @@ -1,4 +1,4 @@ -import com.uwyn.rife2.gradle.TemplateType.* +import com.uwyn.rife2.gradle.TemplateType plugins { id("application") @@ -28,6 +28,7 @@ repositories { rife2 { version = "1.4.0" useAgent = true + precompiledTemplateTypes.add(TemplateType.HTML) } dependencies { diff --git a/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java b/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java deleted file mode 100644 index 8dbf6a6..0000000 --- a/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java +++ /dev/null @@ -1,11 +0,0 @@ -package hello; - -import rife.engine.Server; - -public class AppUber extends App { - public static void main(String[] args) { - new Server() - .staticUberJarResourceBase("webapp") - .start(new AppUber()); - } -} \ No newline at end of file diff --git a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json deleted file mode 100644 index 9b0c3be..0000000 --- a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json +++ /dev/null @@ -1,6 +0,0 @@ -[ -{ - "name":"rife.template.html.hello", - "methods":[{"name":"","parameterTypes":[] }] -} -] diff --git a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json deleted file mode 100644 index ad0b0a3..0000000 --- a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resources":{ - "includes":[ - {"pattern":"^webapp/.*$"} - ] - }, - "bundles":[] -} From 21c85ea93b919d6e5b7af0dac0d8510f80052683 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 15:10:46 +0100 Subject: [PATCH 10/15] Add support for extra template directories By default, the template was using `src/main/templates` as the source directory for templates. This has the benefit of properly isolating the templates from the application classpath: the framework is then responsible for injecting them to the app runtime in whatever suitable form: either untouched as sources (in development mode), or precompiled (for production). With this change, it is also possible to use `src/main/resources/templates` as a source directory. It _may_ feel more natural to users (although I would disagree), but it has the drawback that the jars have to be configured to _exclude_ those resources, because by default whatever is in `src/main/resources` MUST be included in a jar (independently of the run mode). It is also possible for the user to configure additional source directories should they want to. --- app/src/main/java/hello/App.java | 3 +- app/src/main/resources/templates/world.html | 11 +++++ .../rife2/gradle/PrecompileTemplates.java | 40 +++++++++++-------- .../com/uwyn/rife2/gradle/Rife2Plugin.java | 24 ++++++++--- .../src/main/resources/templates/world.html | 11 +++++ .../uwyn/rife2/gradle/PackagingTest.groovy | 3 ++ .../gradle/TemplateCompilationTest.groovy | 1 + 7 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 app/src/main/resources/templates/world.html create mode 100644 build-logic/src/test-projects/minimal/src/main/resources/templates/world.html diff --git a/app/src/main/java/hello/App.java b/app/src/main/java/hello/App.java index a7bed73..f83ec43 100644 --- a/app/src/main/java/hello/App.java +++ b/app/src/main/java/hello/App.java @@ -6,6 +6,7 @@ public class App extends Site { public void setup() { var hello = get("/hello", c -> c.print(c.template("hello"))); get("/", c -> c.redirect(hello)); + get("/world", c -> c.print(c.template("world"))); } public static void main(String[] args) { @@ -13,4 +14,4 @@ public class App extends Site { .staticResourceBase("src/main/webapp") .start(new App()); } -} \ No newline at end of file +} diff --git a/app/src/main/resources/templates/world.html b/app/src/main/resources/templates/world.html new file mode 100644 index 0000000..92637b1 --- /dev/null +++ b/app/src/main/resources/templates/world.html @@ -0,0 +1,11 @@ + + + + + <!--v title-->Hello<!--/v--> + + + +

Hello World from src/main/resources/templates

+ + diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java index 8b9ab9a..eac8e6c 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java @@ -23,7 +23,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; @@ -32,6 +32,7 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.process.ExecOperations; import javax.inject.Inject; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -41,9 +42,9 @@ public abstract class PrecompileTemplates extends DefaultTask { @Classpath public abstract ConfigurableFileCollection getClasspath(); - @InputDirectory + @InputFiles @PathSensitive(PathSensitivity.RELATIVE) - public abstract DirectoryProperty getTemplatesDirectory(); + public abstract ConfigurableFileCollection getTemplatesDirectories(); @Input public abstract ListProperty getTypes(); @@ -65,22 +66,27 @@ public abstract class PrecompileTemplates extends DefaultTask { @TaskAction public void precompileTemplates() { for (var type : getTypes().get()) { - getExecOperations().javaexec(javaexec -> { - javaexec.setClasspath(getClasspath()); - javaexec.getMainClass().set("rife.template.TemplateDeployer"); - List args = new ArrayList<>(); - if (getVerbose().isPresent() && Boolean.TRUE.equals(getVerbose().get())) { - args.add("-verbose"); + getTemplatesDirectories().getFiles().forEach(dir -> { + if (Files.exists(dir.toPath())) { + getExecOperations().javaexec(javaexec -> { + javaexec.setClasspath(getClasspath()); + javaexec.getMainClass().set("rife.template.TemplateDeployer"); + List args = new ArrayList<>(); + if (getVerbose().isPresent() && Boolean.TRUE.equals(getVerbose().get())) { + args.add("-verbose"); + } + args.add("-t"); + args.add(type.identifier()); + args.add("-d"); + args.add(getOutputDirectory().get().getAsFile().getPath()); + args.add("-encoding"); + args.add(getEncoding().orElse("UTF-8").get()); + args.add(dir.getPath()); + javaexec.args(args); + }); } - args.add("-t"); - args.add(type.identifier()); - args.add("-d"); - args.add(getOutputDirectory().get().getAsFile().getPath()); - args.add("-encoding"); - args.add(getEncoding().orElse("UTF-8").get()); - args.add(getTemplatesDirectory().get().getAsFile().getPath()); - javaexec.args(args); }); + } } } diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 2777742..c7054e0 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -15,7 +15,8 @@ */ package com.uwyn.rife2.gradle; -import org.gradle.api.*; +import org.gradle.api.Plugin; +import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; @@ -38,10 +39,11 @@ import org.gradle.api.tasks.testing.Test; import org.gradle.process.CommandLineArgumentProvider; import java.util.Collections; +import java.util.List; import java.util.Locale; public class Rife2Plugin implements Plugin { - public static final String DEFAULT_TEMPLATES_DIR = "src/main/templates"; + public static final List DEFAULT_TEMPLATES_DIRS = List.of("src/main/resources/templates", "src/main/templates"); public static final String DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2"; public static final String RIFE2_GROUP = "rife2"; public static final String WEBAPP_SRCDIR = "src/main/webapp"; @@ -114,7 +116,14 @@ public class Rife2Plugin implements Plugin { private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, TaskProvider precompileTemplatesTask) { - tasks.named("jar", Jar.class, jar -> jar.from(precompileTemplatesTask)); + tasks.named("jar", Jar.class, jar -> { + jar.from(precompileTemplatesTask); + // This isn't great because it needs to be hardcoded, in order to avoid the templates + // declared in `src/main/resources/templates` to be included in the jar file. + // which means that if for whatever reason the user also uses the same directory for + // something else, it will be excluded from the jar file. + jar.exclude("templates"); + }); } private void createRife2DevelopmentOnlyConfiguration(Project project, @@ -125,7 +134,7 @@ public class Rife2Plugin implements Plugin { conf.setCanBeConsumed(false); conf.setCanBeResolved(false); }); - rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(DEFAULT_TEMPLATES_DIR))); + DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(dir)))); configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly); } @@ -149,6 +158,11 @@ public class Rife2Plugin implements Plugin { .filter(f -> f.getAsFile().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) .map(project::zipTree) .toList())); + // This isn't great because it needs to be hardcoded, in order to avoid the templates + // declared in `src/main/resources/templates` to be included in the jar file. + // which means that if for whatever reason the user also uses the same directory for + // something else, it will be excluded from the jar file. + jar.exclude("templates"); plugins.withId("application", unused -> jar.manifest(manifest -> manifest.getAttributes().put("Main-Class", rife2Extension.getUberMainClass().get())) ); @@ -223,7 +237,7 @@ public class Rife2Plugin implements Plugin { task.getVerbose().convention(true); task.getClasspath().from(rife2CompilerClasspath); task.getTypes().convention(rife2Extension.getPrecompiledTemplateTypes()); - task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR)); + DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> task.getTemplatesDirectories().from(project.getLayout().getProjectDirectory().dir(dir))); task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(DEFAULT_GENERATED_RIFE2_CLASSES_DIR)); }); } diff --git a/build-logic/src/test-projects/minimal/src/main/resources/templates/world.html b/build-logic/src/test-projects/minimal/src/main/resources/templates/world.html new file mode 100644 index 0000000..59ff81b --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/resources/templates/world.html @@ -0,0 +1,11 @@ + + + + + <!--v title-->Hello<!--/v--> + + + +

Hello World

+ + diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy index e08ff3c..e3aeb36 100644 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy @@ -25,6 +25,9 @@ class PackagingTest extends AbstractFunctionalTest { } } assert Files.exists(fs.getPath("/rife/template/html/hello.class")) + assert Files.exists(fs.getPath("/rife/template/html/world.class")) + assert !Files.exists(fs.getPath("/templates/hello.html")) + assert !Files.exists(fs.getPath("/templates/world.html")) } where: diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy index 9d2571e..b935f04 100644 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy @@ -42,6 +42,7 @@ class TemplateCompilationTest extends AbstractFunctionalTest { then: "template sources must be present in the classpath" outputContains("Classpath entry: src/main/templates") + outputContains("Classpath entry: src/main/resources/templates") } def "compiles templates when running #task"() { From 0a2b061679b132bba421d3426b4ad5e5b0f23d34 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sun, 5 Mar 2023 09:12:58 -0500 Subject: [PATCH 11/15] Reverted dependencies back to the explicit version --- app/build.gradle.kts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 656dcab..ddab02a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,11 +37,12 @@ rife2 { } dependencies { - runtimeOnly(libs.bundles.jetty) - runtimeOnly(libs.slf4j.simple) + runtimeOnly("org.eclipse.jetty:jetty-server:11.0.13") + runtimeOnly("org.eclipse.jetty:jetty-servlet:11.0.13") + runtimeOnly("org.slf4j:slf4j-simple:2.0.5") - testImplementation(libs.jsoup) - testImplementation(libs.junit.jupiter) + testImplementation("org.jsoup:jsoup:1.15.3") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") } tasks { From a79e616d79d97793febebaa3b97498568c1b53d1 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 16:23:48 +0100 Subject: [PATCH 12/15] Use the official GraalVM plugin --- README.md | 15 ++++++++++----- app/build.gradle.kts | 5 +++++ .../META-INF/native-image/reflect-config.json | 10 +++++++--- settings.gradle.kts | 4 ++++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bbe260e..bb2039a 100644 --- a/README.md +++ b/README.md @@ -92,18 +92,23 @@ GraalVM supports creating a single Ahead-Of-Time [native executable](https://www.graalvm.org/native-image/) from your java bytecode. -Once you have at least GraalVM 22.3.1 Java 19 installed, you can generate the -UberJar as above, then create your native binary as such: +Once you have at least GraalVM 22.3.1 Java 17 installed, you can generate the native binary with: ```bash -native-image --no-fallback --enable-preview -jar app/build/libs/hello-uber-1.0.jar +./gradlew nativeCompile ``` -You'll end up with a `hello-uber-1.0` file that can be executed directly without +You'll end up with a `hello-1.0` file that can be executed directly without the need of a JVM: ```bash -./hello-uber-1.0 +./app/build/native/nativeCompile/hello-1.0 +``` + +Alternatively, you can run the native executable directly with: + +```bash +./gradlew nativeRun ``` > **NOTE:** RIFE2 support for GraalVM native-image is still in preliminary diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 29c1b77..46a872d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,6 +6,7 @@ plugins { application id("com.uwyn.rife2") `maven-publish` + id("org.graalvm.buildtools.native") version "0.9.20" } base { @@ -66,3 +67,7 @@ publishing { } } } + +graalvmNative.binaries.all { + imageName.set("hello-$version") +} diff --git a/app/src/main/resources/META-INF/native-image/reflect-config.json b/app/src/main/resources/META-INF/native-image/reflect-config.json index 9b0c3be..065517e 100644 --- a/app/src/main/resources/META-INF/native-image/reflect-config.json +++ b/app/src/main/resources/META-INF/native-image/reflect-config.json @@ -1,6 +1,10 @@ [ -{ - "name":"rife.template.html.hello", + { + "name":"rife.template.html.hello", + "methods":[{"name":"","parameterTypes":[] }] + }, + { + "name":"rife.template.html.world", "methods":[{"name":"","parameterTypes":[] }] -} + } ] diff --git a/settings.gradle.kts b/settings.gradle.kts index f3be8d5..b4af770 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,8 @@ pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } includeBuild("build-logic") } From d06124c26d14b9d86be392a818c4b3dd9b680ace Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 16:33:36 +0100 Subject: [PATCH 13/15] Make the template directories configurable Adding a directory can be done via the extension: ```gradle rife2 { templateDirectories.from(file("my-template-dir")) } ``` By default the template directories include both `src/main/templates` and `src/main/resources/templates`. If you want to ignore those, then you need to clear the default: ```gradle rife2 { templateDirectories.from.clear() templateDirectories.from(file("my-template-dir")) } ``` --- .../java/com/uwyn/rife2/gradle/Rife2Extension.java | 3 +++ .../java/com/uwyn/rife2/gradle/Rife2Plugin.java | 14 ++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java index 30fa218..c4c641c 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java @@ -15,6 +15,7 @@ */ package com.uwyn.rife2.gradle; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; @@ -26,4 +27,6 @@ public abstract class Rife2Extension { public abstract Property getUberMainClass(); public abstract ListProperty getPrecompiledTemplateTypes(); + + public abstract ConfigurableFileCollection getTemplateDirectories(); } diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index cff2328..aa527b2 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -24,6 +24,7 @@ import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.Bundling; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.ConfigurationVariantDetails; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DuplicatesStrategy; import org.gradle.api.plugins.BasePluginExtension; import org.gradle.api.plugins.JavaApplication; @@ -41,6 +42,7 @@ import org.gradle.process.CommandLineArgumentProvider; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; public class Rife2Plugin implements Plugin { public static final List DEFAULT_TEMPLATES_DIRS = List.of("src/main/resources/templates", "src/main/templates"); @@ -65,7 +67,7 @@ public class Rife2Plugin implements Plugin { configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration); var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath, rife2Extension); - createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler); + createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, rife2Extension.getTemplateDirectories()); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); @@ -128,13 +130,16 @@ public class Rife2Plugin implements Plugin { private void createRife2DevelopmentOnlyConfiguration(Project project, ConfigurationContainer configurations, - DependencyHandler dependencies) { + DependencyHandler dependencies, + ConfigurableFileCollection templateDirectories) { var rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { conf.setDescription("Dependencies which should only be visible when running the application in development mode (and not in tests)."); conf.setCanBeConsumed(false); conf.setCanBeResolved(false); }); - DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(dir)))); + rife2DevelopmentOnly.getDependencies().addAllLater(templateDirectories.getElements().map(locations -> + locations.stream().map(fs -> dependencies.create(project.files(fs))).collect(Collectors.toList())) + ); configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly); } @@ -188,6 +193,7 @@ public class Rife2Plugin implements Plugin { rife2.getUseAgent().convention(false); rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() .map(mainClass -> mainClass + "Uber")); + DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> rife2.getTemplateDirectories().from(project.files(dir))); return rife2; } @@ -236,7 +242,7 @@ public class Rife2Plugin implements Plugin { task.getVerbose().convention(true); task.getClasspath().from(rife2CompilerClasspath); task.getTypes().convention(rife2Extension.getPrecompiledTemplateTypes()); - DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> task.getTemplatesDirectories().from(project.getLayout().getProjectDirectory().dir(dir))); + task.getTemplatesDirectories().from(rife2Extension.getTemplateDirectories()); task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(DEFAULT_GENERATED_RIFE2_CLASSES_DIR)); }); } From a3a5c7c380696785866498cd837408642cb47b1e Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sun, 5 Mar 2023 10:52:34 -0500 Subject: [PATCH 14/15] Made "src/main/resources/templates" the only default template dir. Made the template excludes adapt to the template types that are pre-compiled and use a more restrictive pattern. --- app/src/main/java/hello/App.java | 1 - app/src/main/{ => resources}/templates/hello.html | 0 app/src/main/resources/templates/world.html | 11 ----------- .../main/java/com/uwyn/rife2/gradle/Rife2Plugin.java | 10 ++++++---- build-logic/src/test-projects/minimal/build.gradle | 1 + 5 files changed, 7 insertions(+), 16 deletions(-) rename app/src/main/{ => resources}/templates/hello.html (100%) delete mode 100644 app/src/main/resources/templates/world.html diff --git a/app/src/main/java/hello/App.java b/app/src/main/java/hello/App.java index f83ec43..43a1870 100644 --- a/app/src/main/java/hello/App.java +++ b/app/src/main/java/hello/App.java @@ -6,7 +6,6 @@ public class App extends Site { public void setup() { var hello = get("/hello", c -> c.print(c.template("hello"))); get("/", c -> c.redirect(hello)); - get("/world", c -> c.print(c.template("world"))); } public static void main(String[] args) { diff --git a/app/src/main/templates/hello.html b/app/src/main/resources/templates/hello.html similarity index 100% rename from app/src/main/templates/hello.html rename to app/src/main/resources/templates/hello.html diff --git a/app/src/main/resources/templates/world.html b/app/src/main/resources/templates/world.html deleted file mode 100644 index 92637b1..0000000 --- a/app/src/main/resources/templates/world.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - <!--v title-->Hello<!--/v--> - - - -

Hello World from src/main/resources/templates

- - diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index aa527b2..985e06f 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -45,7 +45,7 @@ import java.util.Locale; import java.util.stream.Collectors; public class Rife2Plugin implements Plugin { - public static final List DEFAULT_TEMPLATES_DIRS = List.of("src/main/resources/templates", "src/main/templates"); + public static final List DEFAULT_TEMPLATES_DIRS = List.of("src/main/resources/templates"); public static final String DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2"; public static final String RIFE2_GROUP = "rife2"; public static final String WEBAPP_SRCDIR = "src/main/webapp"; @@ -71,7 +71,7 @@ public class Rife2Plugin implements Plugin { exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); - bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates); + bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates, rife2Extension); configureMavenPublishing(project, plugins, configurations, uberJarTask); } @@ -117,14 +117,16 @@ public class Rife2Plugin implements Plugin { } private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, - TaskProvider precompileTemplatesTask) { + TaskProvider precompileTemplatesTask, + Rife2Extension rife2Extension) { tasks.named("jar", Jar.class, jar -> { jar.from(precompileTemplatesTask); // This isn't great because it needs to be hardcoded, in order to avoid the templates // declared in `src/main/resources/templates` to be included in the jar file. // which means that if for whatever reason the user also uses the same directory for // something else, it will be excluded from the jar file. - jar.exclude("templates"); + rife2Extension.getPrecompiledTemplateTypes().get().forEach(templateType -> jar.exclude("/templates/**." + templateType.identifier().toLowerCase()) + ); }); } diff --git a/build-logic/src/test-projects/minimal/build.gradle b/build-logic/src/test-projects/minimal/build.gradle index 5bc074f..2212c0f 100644 --- a/build-logic/src/test-projects/minimal/build.gradle +++ b/build-logic/src/test-projects/minimal/build.gradle @@ -29,6 +29,7 @@ rife2 { version = "1.4.0" useAgent = true precompiledTemplateTypes.add(TemplateType.HTML) + templateDirectories.from(file("src/main/templates")) } dependencies { From 938372addc28d84435f3f09dcc9f2477cc9a78c2 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sun, 5 Mar 2023 10:56:37 -0500 Subject: [PATCH 15/15] Made GraalVM plugin work with Jetty and JDK 19 --- app/build.gradle.kts | 1 + .../main/resources/META-INF/native-image/reflect-config.json | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 72a0cfb..0193db3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -71,5 +71,6 @@ publishing { } graalvmNative.binaries.all { + buildArgs.add("--enable-preview") // support for Jetty virtual threads with JDK 19 imageName.set("hello-$version") } diff --git a/app/src/main/resources/META-INF/native-image/reflect-config.json b/app/src/main/resources/META-INF/native-image/reflect-config.json index 065517e..3f485d5 100644 --- a/app/src/main/resources/META-INF/native-image/reflect-config.json +++ b/app/src/main/resources/META-INF/native-image/reflect-config.json @@ -2,9 +2,5 @@ { "name":"rife.template.html.hello", "methods":[{"name":"","parameterTypes":[] }] - }, - { - "name":"rife.template.html.world", - "methods":[{"name":"","parameterTypes":[] }] } ]