diff --git a/README.md b/README.md index bb2039a..bbe260e 100644 --- a/README.md +++ b/README.md @@ -92,23 +92,18 @@ 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 17 installed, you can generate the native binary with: +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: ```bash -./gradlew nativeCompile +native-image --no-fallback --enable-preview -jar app/build/libs/hello-uber-1.0.jar ``` -You'll end up with a `hello-1.0` file that can be executed directly without +You'll end up with a `hello-uber-1.0` file that can be executed directly without the need of a JVM: ```bash -./app/build/native/nativeCompile/hello-1.0 -``` - -Alternatively, you can run the native executable directly with: - -```bash -./gradlew nativeRun +./hello-uber-1.0 ``` > **NOTE:** RIFE2 support for GraalVM native-image is still in preliminary diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0193db3..df9f28e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,22 +1,16 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent -import com.uwyn.rife2.gradle.TemplateType.* plugins { - application id("com.uwyn.rife2") `maven-publish` - id("org.graalvm.buildtools.native") version "0.9.20" } +version = 1.0 +group = "com.example" + base { archivesName.set("hello") - version = 1.0 - group = "com.example" -} - -application { - mainClass.set("hello.App") } java { @@ -32,9 +26,9 @@ repositories { } rife2 { - version.set("1.4.0") + mainClass.set("hello.App") + version.set("1.3.0") useAgent.set(true) - precompiledTemplateTypes.add(HTML) } dependencies { @@ -69,8 +63,3 @@ 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/java/hello/App.java b/app/src/main/java/hello/App.java index 43a1870..a7bed73 100644 --- a/app/src/main/java/hello/App.java +++ b/app/src/main/java/hello/App.java @@ -13,4 +13,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/META-INF/native-image/reflect-config.json b/app/src/main/resources/META-INF/native-image/reflect-config.json index 3f485d5..9b0c3be 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,6 @@ [ - { - "name":"rife.template.html.hello", - "methods":[{"name":"","parameterTypes":[] }] - } +{ + "name":"rife.template.html.hello", + "methods":[{"name":"","parameterTypes":[] }] +} ] diff --git a/app/src/main/resources/templates/hello.html b/app/src/main/templates/hello.html similarity index 96% rename from app/src/main/resources/templates/hello.html rename to app/src/main/templates/hello.html index 59ff81b..f7ffe8b 100644 --- a/app/src/main/resources/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 c7fabff..b9ea647 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,6 +1,5 @@ plugins { `java-gradle-plugin` - groovy } repositories { @@ -9,8 +8,13 @@ repositories { dependencies { gradleApi() - testImplementation(libs.spock.core) - testImplementation(gradleTestKit()) +} + +tasks { + withType { + options.isDeprecation = true + options.compilerArgs.add("-Xlint:unchecked") + } } gradlePlugin { @@ -21,11 +25,3 @@ 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 9ac59bd..7fbbd44 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1,9 +1 @@ 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/PrecompileTemplates.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/PrecompileTemplates.java index eac8e6c..871df2e 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,12 +18,11 @@ 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; import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.InputDirectory; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; @@ -32,7 +31,6 @@ 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; @@ -42,12 +40,12 @@ public abstract class PrecompileTemplates extends DefaultTask { @Classpath public abstract ConfigurableFileCollection getClasspath(); - @InputFiles + @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) - public abstract ConfigurableFileCollection getTemplatesDirectories(); + public abstract DirectoryProperty getTemplatesDirectory(); @Input - public abstract ListProperty getTypes(); + public abstract Property getType(); @Input @Optional @@ -65,28 +63,21 @@ public abstract class PrecompileTemplates extends DefaultTask { @TaskAction public void precompileTemplates() { - for (var type : getTypes().get()) { - 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); - }); - } - }); - - } + 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); + }); } } 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 c4c641c..ebd97e0 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,18 +15,15 @@ */ package com.uwyn.rife2.gradle; -import org.gradle.api.file.ConfigurableFileCollection; -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(); 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 985e06f..17bfd18 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 @@ -21,35 +21,29 @@ 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; -import org.gradle.api.file.ConfigurableFileCollection; 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.JavaExec; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskContainer; -import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.*; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.testing.Test; import org.gradle.process.CommandLineArgumentProvider; import java.util.Collections; -import java.util.List; import java.util.Locale; -import java.util.stream.Collectors; +@SuppressWarnings({"ALL", "unused"}) public class Rife2Plugin implements Plugin { - public static final List DEFAULT_TEMPLATES_DIRS = List.of("src/main/resources/templates"); + public static final String DEFAULT_TEMPLATES_DIR = "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"; - public static final String PRECOMPILE_TEMPLATES_TASK_NAME = "precompileTemplates"; @Override public void apply(Project project) { @@ -60,88 +54,67 @@ 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 precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath, rife2Extension); - createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, rife2Extension.getTemplateDirectories()); + var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath); + createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); + registerRunTask(project, rife2Extension, rife2AgentClasspath); TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); - bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates, rife2Extension); - + bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates); configureMavenPublishing(project, plugins, configurations, uberJarTask); } - @SuppressWarnings("unchecked") - private static void configureMavenPublishing(Project project, - PluginContainer plugins, - ConfigurationContainer configurations, - TaskProvider uberJarTask) { + private static void configureMavenPublishing(Project project, PluginContainer plugins, ConfigurationContainer configurations, TaskProvider uberJarTask) { plugins.withId("maven-publish", unused -> { - var rife2UberJarElements = configurations.create("rife2UberJarElements", conf -> { + Configuration 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")); - - var runtimeAttributes = configurations.getByName(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME).getAttributes(); + AttributeContainer runtimeAttributes = configurations.getByName(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME).getAttributes(); conf.attributes(attrs -> { for (Attribute attribute : runtimeAttributes.keySet()) { Object value = runtimeAttributes.getAttribute(attribute); - //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); + 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); + } } } }); }); - - var component = (AdhocComponentWithVariants) project.getComponents().getByName("java"); + AdhocComponentWithVariants component = (AdhocComponentWithVariants) project.getComponents().getByName("java"); component.addVariantsFromConfiguration(rife2UberJarElements, ConfigurationVariantDetails::mapToOptional); }); } - private static void exposePrecompiledTemplatesToTestTask(Project project, - ConfigurationContainer configurations, - DependencyHandler dependencyHandler, - TaskProvider precompileTemplatesTask) { + 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(precompileTemplatesTask))); + .getDependencies() + .add(dependencyHandler.create(project.files(precompileTemplates))); } - private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, - 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. - rife2Extension.getPrecompiledTemplateTypes().get().forEach(templateType -> jar.exclude("/templates/**." + templateType.identifier().toLowerCase()) - ); - }); + 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, - ConfigurableFileCollection templateDirectories) { - var rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { + TaskProvider precompileTemplatesTask) { + Configuration 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().addAllLater(templateDirectories.getElements().map(locations -> - locations.stream().map(fs -> dependencies.create(project.files(fs))).collect(Collectors.toList())) - ); + rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(precompileTemplatesTask))); configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly); } @@ -157,29 +130,21 @@ 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) - .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"); + .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()); @@ -193,14 +158,11 @@ 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().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() - .map(mainClass -> mainClass + "Uber")); - DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> rife2.getTemplateDirectories().from(project.files(dir))); + rife2.getUberMainClass().set(rife2.getMainClass() + "Uber"); 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); @@ -236,16 +198,28 @@ public class Rife2Plugin implements Plugin { } private static TaskProvider registerPrecompileTemplateTask(Project project, - Configuration rife2CompilerClasspath, - Rife2Extension rife2Extension) { - return project.getTasks().register(PRECOMPILE_TEMPLATES_TASK_NAME, PrecompileTemplates.class, task -> { + Configuration rife2CompilerClasspath) { + return project.getTasks().register("precompileTemplates", PrecompileTemplates.class, task -> { task.setGroup(RIFE2_GROUP); task.setDescription("Pre-compiles the templates."); task.getVerbose().convention(true); task.getClasspath().from(rife2CompilerClasspath); - task.getTypes().convention(rife2Extension.getPrecompiledTemplateTypes()); - task.getTemplatesDirectories().from(rife2Extension.getTemplateDirectories()); + task.getType().convention("html"); + task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR)); 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 new file mode 100644 index 0000000..c107544 --- /dev/null +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java @@ -0,0 +1,54 @@ +/* + * 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/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java deleted file mode 100644 index 6fff9a7..0000000 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/TemplateType.java +++ /dev/null @@ -1,25 +0,0 @@ -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"); - 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 diff --git a/build-logic/src/test-projects/minimal/build.gradle b/build-logic/src/test-projects/minimal/build.gradle deleted file mode 100644 index 2212c0f..0000000 --- a/build-logic/src/test-projects/minimal/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -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 - precompiledTemplateTypes.add(TemplateType.HTML) - templateDirectories.from(file("src/main/templates")) -} - -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 deleted file mode 100644 index e87ab3c..0000000 --- a/build-logic/src/test-projects/minimal/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 43a1870..0000000 --- a/build-logic/src/test-projects/minimal/src/main/java/hello/App.java +++ /dev/null @@ -1,16 +0,0 @@ -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/resources/templates/world.html b/build-logic/src/test-projects/minimal/src/main/resources/templates/world.html deleted file mode 100644 index 59ff81b..0000000 --- a/build-logic/src/test-projects/minimal/src/main/resources/templates/world.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - <!--v title-->Hello<!--/v--> - - - -

Hello World

- - 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 deleted file mode 100644 index 59ff81b..0000000 --- a/build-logic/src/test-projects/minimal/src/main/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - <!--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 deleted file mode 100644 index 52bf6c7..0000000 --- a/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css +++ /dev/null @@ -1,21 +0,0 @@ -: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 deleted file mode 100644 index 45a0763..0000000 --- a/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 deleted file mode 100644 index 7629d3f..0000000 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy +++ /dev/null @@ -1,184 +0,0 @@ -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 deleted file mode 100644 index e3aeb36..0000000 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy +++ /dev/null @@ -1,38 +0,0 @@ -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")) - 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: - 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 deleted file mode 100644 index 89f60c1..0000000 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy +++ /dev/null @@ -1,81 +0,0 @@ -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 deleted file mode 100644 index b935f04..0000000 --- a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy +++ /dev/null @@ -1,60 +0,0 @@ -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 { - def rootPath = rootProject.projectDir.toPath() - tasks.named("run").get().classpath.files.each { - println "Classpath entry: \${rootPath.relativize(it.toPath())}" - } - } - } - """ - - when: - run("dumpRunClasspath") - - 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"() { - 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 deleted file mode 100644 index 3a8c200..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,17 +0,0 @@ -[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" ] diff --git a/settings.gradle.kts b/settings.gradle.kts index b4af770..f3be8d5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,4 @@ pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } includeBuild("build-logic") } diff --git a/war/build.gradle.kts b/war/build.gradle.kts index 206f003..477784d 100644 --- a/war/build.gradle.kts +++ b/war/build.gradle.kts @@ -2,9 +2,10 @@ plugins { war } +version = 1.0 + base { archivesName.set("hello") - version = 1.0 } repositories { @@ -20,4 +21,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 +}