2
0
Fork 0
mirror of https://github.com/ethauvin/rife2-hello.git synced 2025-04-29 08:58:12 -07:00

Compare commits

..

No commits in common. "938372addc28d84435f3f09dcc9f2477cc9a78c2" and "f8b3078ba11682402e2074de3e682556be602d4c" have entirely different histories.

26 changed files with 155 additions and 701 deletions

View file

@ -92,23 +92,18 @@ GraalVM supports creating a single Ahead-Of-Time
[native executable](https://www.graalvm.org/native-image/) from your java [native executable](https://www.graalvm.org/native-image/) from your java
bytecode. 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 ```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: the need of a JVM:
```bash ```bash
./app/build/native/nativeCompile/hello-1.0 ./hello-uber-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 > **NOTE:** RIFE2 support for GraalVM native-image is still in preliminary

View file

@ -1,22 +1,16 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent import org.gradle.api.tasks.testing.logging.TestLogEvent
import com.uwyn.rife2.gradle.TemplateType.*
plugins { plugins {
application
id("com.uwyn.rife2") id("com.uwyn.rife2")
`maven-publish` `maven-publish`
id("org.graalvm.buildtools.native") version "0.9.20"
} }
version = 1.0
group = "com.example"
base { base {
archivesName.set("hello") archivesName.set("hello")
version = 1.0
group = "com.example"
}
application {
mainClass.set("hello.App")
} }
java { java {
@ -32,9 +26,9 @@ repositories {
} }
rife2 { rife2 {
version.set("1.4.0") mainClass.set("hello.App")
version.set("1.3.0")
useAgent.set(true) useAgent.set(true)
precompiledTemplateTypes.add(HTML)
} }
dependencies { 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")
}

View file

@ -13,4 +13,4 @@ public class App extends Site {
.staticResourceBase("src/main/webapp") .staticResourceBase("src/main/webapp")
.start(new App()); .start(new App());
} }
} }

View file

@ -1,6 +1,6 @@
[ [
{ {
"name":"rife.template.html.hello", "name":"rife.template.html.hello",
"methods":[{"name":"<init>","parameterTypes":[] }] "methods":[{"name":"<init>","parameterTypes":[] }]
} }
] ]

View file

@ -8,4 +8,4 @@
<body> <body>
<p>Hello World</p> <p>Hello World</p>
</body> </body>
</html> </html>

View file

@ -1,6 +1,5 @@
plugins { plugins {
`java-gradle-plugin` `java-gradle-plugin`
groovy
} }
repositories { repositories {
@ -9,8 +8,13 @@ repositories {
dependencies { dependencies {
gradleApi() gradleApi()
testImplementation(libs.spock.core) }
testImplementation(gradleTestKit())
tasks {
withType<JavaCompile> {
options.isDeprecation = true
options.compilerArgs.add("-Xlint:unchecked")
}
} }
gradlePlugin { gradlePlugin {
@ -21,11 +25,3 @@ gradlePlugin {
} }
} }
} }
tasks.withType<Test>().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)
}
}

View file

@ -1,9 +1 @@
rootProject.name = "build-logic" rootProject.name = "build-logic"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View file

@ -18,12 +18,11 @@ package com.uwyn.rife2.gradle;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input; 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.Optional;
import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitive;
@ -32,7 +31,6 @@ import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecOperations; import org.gradle.process.ExecOperations;
import javax.inject.Inject; import javax.inject.Inject;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -42,12 +40,12 @@ public abstract class PrecompileTemplates extends DefaultTask {
@Classpath @Classpath
public abstract ConfigurableFileCollection getClasspath(); public abstract ConfigurableFileCollection getClasspath();
@InputFiles @InputDirectory
@PathSensitive(PathSensitivity.RELATIVE) @PathSensitive(PathSensitivity.RELATIVE)
public abstract ConfigurableFileCollection getTemplatesDirectories(); public abstract DirectoryProperty getTemplatesDirectory();
@Input @Input
public abstract ListProperty<TemplateType> getTypes(); public abstract Property<String> getType();
@Input @Input
@Optional @Optional
@ -65,28 +63,21 @@ public abstract class PrecompileTemplates extends DefaultTask {
@TaskAction @TaskAction
public void precompileTemplates() { public void precompileTemplates() {
for (var type : getTypes().get()) { getExecOperations().javaexec(javaexec -> {
getTemplatesDirectories().getFiles().forEach(dir -> { javaexec.setClasspath(getClasspath());
if (Files.exists(dir.toPath())) { javaexec.getMainClass().set("rife.template.TemplateDeployer");
getExecOperations().javaexec(javaexec -> { List<String> args = new ArrayList<>();
javaexec.setClasspath(getClasspath()); if (getVerbose().isPresent() && Boolean.TRUE.equals(getVerbose().get())) {
javaexec.getMainClass().set("rife.template.TemplateDeployer"); args.add("-verbose");
List<String> args = new ArrayList<>(); }
if (getVerbose().isPresent() && Boolean.TRUE.equals(getVerbose().get())) { args.add("-t");
args.add("-verbose"); args.add(getType().get());
} args.add("-d");
args.add("-t"); args.add(getOutputDirectory().get().getAsFile().getPath());
args.add(type.identifier()); args.add("-encoding");
args.add("-d"); args.add(getEncoding().orElse("UTF-8").get());
args.add(getOutputDirectory().get().getAsFile().getPath()); args.add(getTemplatesDirectory().get().getAsFile().getPath());
args.add("-encoding"); javaexec.args(args);
args.add(getEncoding().orElse("UTF-8").get()); });
args.add(dir.getPath());
javaexec.args(args);
});
}
});
}
} }
} }

View file

@ -15,18 +15,15 @@
*/ */
package com.uwyn.rife2.gradle; package com.uwyn.rife2.gradle;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
@SuppressWarnings("unused")
public abstract class Rife2Extension { public abstract class Rife2Extension {
public abstract Property<String> getMainClass();
public abstract Property<String> getVersion(); public abstract Property<String> getVersion();
public abstract Property<Boolean> getUseAgent(); public abstract Property<Boolean> getUseAgent();
public abstract Property<String> getUberMainClass(); public abstract Property<String> getUberMainClass();
public abstract ListProperty<TemplateType> getPrecompiledTemplateTypes();
public abstract ConfigurableFileCollection getTemplateDirectories();
} }

View file

@ -21,35 +21,29 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeContainer;
import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.Bundling;
import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.AdhocComponentWithVariants;
import org.gradle.api.component.ConfigurationVariantDetails; import org.gradle.api.component.ConfigurationVariantDetails;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DuplicatesStrategy; import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.plugins.BasePluginExtension; import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.PluginContainer; import org.gradle.api.plugins.PluginContainer;
import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.*;
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.bundling.Jar;
import org.gradle.api.tasks.testing.Test; import org.gradle.api.tasks.testing.Test;
import org.gradle.process.CommandLineArgumentProvider; import org.gradle.process.CommandLineArgumentProvider;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Collectors;
@SuppressWarnings({"ALL", "unused"})
public class Rife2Plugin implements Plugin<Project> { public class Rife2Plugin implements Plugin<Project> {
public static final List<String> 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 DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2";
public static final String RIFE2_GROUP = "rife2"; public static final String RIFE2_GROUP = "rife2";
public static final String WEBAPP_SRCDIR = "src/main/webapp"; public static final String WEBAPP_SRCDIR = "src/main/webapp";
public static final String PRECOMPILE_TEMPLATES_TASK_NAME = "precompileTemplates";
@Override @Override
public void apply(Project project) { public void apply(Project project) {
@ -60,88 +54,67 @@ public class Rife2Plugin implements Plugin<Project> {
var configurations = project.getConfigurations(); var configurations = project.getConfigurations();
var dependencyHandler = project.getDependencies(); var dependencyHandler = project.getDependencies();
var tasks = project.getTasks(); var tasks = project.getTasks();
var rife2Configuration = createRife2Configuration(configurations, dependencyHandler, rife2Extension); var rife2Configuration = createRife2Configuration(configurations, dependencyHandler, rife2Extension);
var rife2CompilerClasspath = createRife2CompilerClasspathConfiguration(configurations, rife2Configuration); var rife2CompilerClasspath = createRife2CompilerClasspathConfiguration(configurations, rife2Configuration);
var rife2AgentClasspath = createRife2AgentConfiguration(configurations, dependencyHandler, rife2Extension); var rife2AgentClasspath = createRife2AgentConfiguration(configurations, dependencyHandler, rife2Extension);
configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration); configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration);
var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath);
var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath, rife2Extension); createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates);
createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, rife2Extension.getTemplateDirectories());
exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates);
configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath);
registerRunTask(project, rife2Extension, rife2AgentClasspath);
TaskProvider<Jar> uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); TaskProvider<Jar> uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates);
bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates, rife2Extension); bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates);
configureMavenPublishing(project, plugins, configurations, uberJarTask); configureMavenPublishing(project, plugins, configurations, uberJarTask);
} }
@SuppressWarnings("unchecked") private static void configureMavenPublishing(Project project, PluginContainer plugins, ConfigurationContainer configurations, TaskProvider<Jar> uberJarTask) {
private static void configureMavenPublishing(Project project,
PluginContainer plugins,
ConfigurationContainer configurations,
TaskProvider<Jar> uberJarTask) {
plugins.withId("maven-publish", unused -> { 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.setDescription("Exposes the uber jar archive of the RIFE2 web application.");
conf.setCanBeResolved(false); conf.setCanBeResolved(false);
conf.setCanBeConsumed(true); conf.setCanBeConsumed(true);
conf.getOutgoing().artifact(uberJarTask, artifact -> artifact.setClassifier("uber")); 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 -> { conf.attributes(attrs -> {
for (Attribute<?> attribute : runtimeAttributes.keySet()) { for (Attribute<?> attribute : runtimeAttributes.keySet()) {
Object value = runtimeAttributes.getAttribute(attribute); Object value = runtimeAttributes.getAttribute(attribute);
//noinspection unchecked if (value != null) {
if (Bundling.class.equals(attribute.getType())) { if (Bundling.class.equals(attribute.getType())) {
attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED)); attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED));
} else { } else {
attrs.attribute((Attribute<Object>) attribute, value); //noinspection unchecked
attrs.attribute((Attribute<Object>) attribute, value);
}
} }
} }
}); });
}); });
AdhocComponentWithVariants component = (AdhocComponentWithVariants) project.getComponents().getByName("java");
var component = (AdhocComponentWithVariants) project.getComponents().getByName("java");
component.addVariantsFromConfiguration(rife2UberJarElements, ConfigurationVariantDetails::mapToOptional); component.addVariantsFromConfiguration(rife2UberJarElements, ConfigurationVariantDetails::mapToOptional);
}); });
} }
private static void exposePrecompiledTemplatesToTestTask(Project project, private static void exposePrecompiledTemplatesToTestTask(Project project, ConfigurationContainer configurations, DependencyHandler dependencyHandler, TaskProvider<PrecompileTemplates> precompileTemplates) {
ConfigurationContainer configurations,
DependencyHandler dependencyHandler,
TaskProvider<PrecompileTemplates> precompileTemplatesTask) {
configurations.getByName(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME) configurations.getByName(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME)
.getDependencies() .getDependencies()
.add(dependencyHandler.create(project.files(precompileTemplatesTask))); .add(dependencyHandler.create(project.files(precompileTemplates)));
} }
private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, private static void bundlePrecompiledTemplatesIntoJarFile(TaskContainer tasks, TaskProvider<PrecompileTemplates> precompileTemplates) {
TaskProvider<PrecompileTemplates> precompileTemplatesTask, tasks.named("jar", Jar.class, jar -> jar.from(precompileTemplates));
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 void createRife2DevelopmentOnlyConfiguration(Project project, private void createRife2DevelopmentOnlyConfiguration(Project project,
ConfigurationContainer configurations, ConfigurationContainer configurations,
DependencyHandler dependencies, DependencyHandler dependencies,
ConfigurableFileCollection templateDirectories) { TaskProvider<PrecompileTemplates> precompileTemplatesTask) {
var rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { 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.setDescription("Dependencies which should only be visible when running the application in development mode (and not in tests).");
conf.setCanBeConsumed(false); conf.setCanBeConsumed(false);
conf.setCanBeResolved(false); conf.setCanBeResolved(false);
}); });
rife2DevelopmentOnly.getDependencies().addAllLater(templateDirectories.getElements().map(locations -> rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(precompileTemplatesTask)));
locations.stream().map(fs -> dependencies.create(project.files(fs))).collect(Collectors.toList()))
);
configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly); configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly);
} }
@ -157,29 +130,21 @@ public class Rife2Plugin implements Plugin<Project> {
var base = project.getExtensions().getByType(BasePluginExtension.class); var base = project.getExtensions().getByType(BasePluginExtension.class);
jar.getArchiveBaseName().convention(project.provider(() -> base.getArchivesName().get() + "-uber")); jar.getArchiveBaseName().convention(project.provider(() -> base.getArchivesName().get() + "-uber"));
jar.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE); 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(javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput());
jar.from(precompileTemplatesTask); jar.from(precompileTemplatesTask);
jar.into("webapp", spec -> spec.from(WEBAPP_SRCDIR)); 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() jar.from(runtimeClasspath.getElements().map(e -> e.stream()
.filter(f -> f.getAsFile().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) .filter(f -> f.getAsFile().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar"))
.map(project::zipTree) .map(project::zipTree)
.toList())); .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 -> plugins.withId("application", unused -> jar.manifest(manifest ->
manifest.getAttributes().put("Main-Class", rife2Extension.getUberMainClass().get())) manifest.getAttributes().put("Main-Class", rife2Extension.getUberMainClass().get()))
); );
}); });
} }
private static void configureAgent(Project project, private static void configureAgent(Project project, PluginContainer plugins, Rife2Extension rife2Extension, Configuration rife2AgentClasspath) {
PluginContainer plugins,
Rife2Extension rife2Extension,
Configuration rife2AgentClasspath) {
CommandLineArgumentProvider agentProvider = () -> { CommandLineArgumentProvider agentProvider = () -> {
if (Boolean.TRUE.equals(rife2Extension.getUseAgent().get())) { if (Boolean.TRUE.equals(rife2Extension.getUseAgent().get())) {
return Collections.singleton("-javaagent:" + rife2AgentClasspath.getAsPath()); return Collections.singleton("-javaagent:" + rife2AgentClasspath.getAsPath());
@ -193,14 +158,11 @@ public class Rife2Plugin implements Plugin<Project> {
private static Rife2Extension createRife2Extension(Project project) { private static Rife2Extension createRife2Extension(Project project) {
var rife2 = project.getExtensions().create("rife2", Rife2Extension.class); var rife2 = project.getExtensions().create("rife2", Rife2Extension.class);
rife2.getUseAgent().convention(false); rife2.getUseAgent().convention(false);
rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() rife2.getUberMainClass().set(rife2.getMainClass() + "Uber");
.map(mainClass -> mainClass + "Uber"));
DEFAULT_TEMPLATES_DIRS.stream().forEachOrdered(dir -> rife2.getTemplateDirectories().from(project.files(dir)));
return rife2; return rife2;
} }
private static Configuration createRife2CompilerClasspathConfiguration(ConfigurationContainer configurations, private static Configuration createRife2CompilerClasspathConfiguration(ConfigurationContainer configurations, Configuration rife2Configuration) {
Configuration rife2Configuration) {
return configurations.create("rife2CompilerClasspath", conf -> { return configurations.create("rife2CompilerClasspath", conf -> {
conf.setDescription("The RIFE2 compiler classpath"); conf.setDescription("The RIFE2 compiler classpath");
conf.setCanBeConsumed(false); conf.setCanBeConsumed(false);
@ -236,16 +198,28 @@ public class Rife2Plugin implements Plugin<Project> {
} }
private static TaskProvider<PrecompileTemplates> registerPrecompileTemplateTask(Project project, private static TaskProvider<PrecompileTemplates> registerPrecompileTemplateTask(Project project,
Configuration rife2CompilerClasspath, 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.setGroup(RIFE2_GROUP);
task.setDescription("Pre-compiles the templates."); task.setDescription("Pre-compiles the templates.");
task.getVerbose().convention(true); task.getVerbose().convention(true);
task.getClasspath().from(rife2CompilerClasspath); task.getClasspath().from(rife2CompilerClasspath);
task.getTypes().convention(rife2Extension.getPrecompiledTemplateTypes()); task.getType().convention("html");
task.getTemplatesDirectories().from(rife2Extension.getTemplateDirectories()); task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR));
task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(DEFAULT_GENERATED_RIFE2_CLASSES_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));
});
}
} }

View file

@ -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<String> getAgentClassPath();
@Classpath
public abstract ConfigurableFileCollection getClasspath();
@Inject
protected abstract ExecOperations getExecOperations();
@Input
public abstract Property<String> 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()));
});
}
}

View file

@ -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_;
}
}

View file

@ -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")
}

View file

@ -1 +0,0 @@
rootProject.name = 'minimal'

View file

@ -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());
}
}

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><!--v title-->Hello<!--/v--></title>
<link rel="stylesheet" href="{{v webapp:rootUrl/}}css/style.css?{{v context:paramRandom/}}">
</head>
<body>
<p>Hello World</p>
</body>
</html>

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><!--v title-->Hello<!--/v--></title>
<link rel="stylesheet" href="{{v webapp:rootUrl/}}css/style.css?{{v context:paramRandom/}}">
</head>
<body>
<p>Hello World</p>
</body>
</html>

View file

@ -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);
}

View file

@ -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"));
}
}

View file

@ -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<String> 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<String> computeAutoArgs() {
List<String> 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)
}
}

View file

@ -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'
}
}

View file

@ -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()
}
}
}

View file

@ -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']
}
}

View file

@ -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" ]

View file

@ -1,8 +1,4 @@
pluginManagement { pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
includeBuild("build-logic") includeBuild("build-logic")
} }

View file

@ -2,9 +2,10 @@ plugins {
war war
} }
version = 1.0
base { base {
archivesName.set("hello") archivesName.set("hello")
version = 1.0
} }
repositories { repositories {
@ -20,4 +21,4 @@ tasks.war {
webAppDirectory.set(file("../app/src/main/webapp")) webAppDirectory.set(file("../app/src/main/webapp"))
webXml = file("src/web.xml") webXml = file("src/web.xml")
rootSpec.exclude("**/jetty*.jar", "**/slf4j*.jar", "**/rife2*-agent.jar") rootSpec.exclude("**/jetty*.jar", "**/slf4j*.jar", "**/rife2*-agent.jar")
} }