2
0
Fork 0
mirror of https://github.com/ethauvin/rife2-hello.git synced 2025-04-24 15:07:11 -07:00

Rework Gradle build

This commit introduces a _convention plugin_ for RIFE2 support.
It provides a number of advantages:

- the build logic is clearly separated from the build script,
which now only contains user-specific configuration, like the
framework version or how to configure test logging
- it fixes a number of issues like hardcoded dependencies
(`dependsOn` is in general a mistake)
- it removes the need to resolve the agent path eagerly
- it makes the agent configurable
- it clearly separates the user classpath from the RIFE
classpath (both for precompiling templates and for the agent)
- template compilation is cached
- it avoids the abuse of `src/main/resources` to put
templates and uses a dedicated directory instead, which
removes the need for exclusions

In addition, this should make it relatively straightforward
to convert the convention plugin into a proper RIFE2 plugin,
by extracting the code in `build-logic` into its own
repository then publishing to the Gradle plugin portal.
This commit is contained in:
Cedric Champeau 2023-02-21 23:33:54 +01:00
parent 3a9bbec851
commit 2e025cd693
9 changed files with 284 additions and 77 deletions

View file

@ -2,7 +2,8 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
plugins {
java
application
id("com.uwyn.rife2")
}
base {
@ -10,25 +11,22 @@ base {
version = 1.0
}
application {
mainClass.set("hello.App")
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
maven { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots") } // only needed for SNAPSHOT
}
sourceSets {
main {
runtimeClasspath = files(file("src/main/resources"), runtimeClasspath);
}
}
sourceSets.main {
resources.exclude("templates/**")
rife2 {
version.set("1.3.0")
useAgent.set(true)
}
dependencies {
implementation("com.uwyn.rife2:rife2:1.3.0")
runtimeOnly("com.uwyn.rife2:rife2:1.3.0:agent")
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")
@ -38,68 +36,11 @@ dependencies {
}
tasks {
val dependencies = configurations
.runtimeClasspath.get().files;
val rifeAgentJar = dependencies
.filter { it.toString().contains("rife2") }
.filter { it.toString().endsWith("-agent.jar") }[0]
test {
jvmArgs = listOf("-javaagent:$rifeAgentJar")
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}
// Pre-compile the RIFE2 templates to bytecode for deployment
register<JavaExec>("precompileHtmlTemplates") {
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("rife.template.TemplateDeployer")
args = listOf(
"-verbose",
"-t", "html",
"-d", "${projectDir}/build/classes/java/main",
"-encoding", "UTF-8", "${projectDir}/src/main/resources/templates"
)
}
register("precompileTemplates") {
dependsOn("precompileHtmlTemplates")
}
// Ensure that the templates are pre-compiled before building the jar
jar {
dependsOn("precompileTemplates")
}
// Replace the run task with one that uses the RIFE2 agent
register<JavaExec>("run") {
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("hello.App")
jvmArgs = listOf("-javaagent:$rifeAgentJar")
}
// These two tasks create a self-container UberJar
register<Copy>("copyWebapp") {
from("src/main/")
include("webapp/**")
into("$buildDir/webapp")
}
register<Jar>("uberJar") {
dependsOn("jar")
dependsOn("copyWebapp")
archiveBaseName.set("hello-uber")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes["Main-Class"] = "hello.AppUber"
}
val uberDependencies = dependencies
.filter { !it.toString().matches("rife2-.*agent\\.jar".toRegex()) }
.map(::zipTree)
from(uberDependencies, "$buildDir/webapp")
with(jar.get())
}
}
}

View file

@ -0,0 +1,20 @@
plugins {
`java-gradle-plugin`
}
repositories {
mavenCentral()
}
dependencies {
gradleApi()
}
gradlePlugin {
plugins {
create("rife2") {
id = "com.uwyn.rife2"
implementationClass = "com.uwyn.rife2.gradle.Rife2Plugin"
}
}
}

View file

@ -0,0 +1 @@
rootProject.name = "build-logic"

View file

@ -0,0 +1,83 @@
/*
* 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.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecOperations;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@CacheableTask
public abstract class PrecompileTemplates extends DefaultTask {
@Classpath
public abstract ConfigurableFileCollection getClasspath();
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
public abstract DirectoryProperty getTemplatesDirectory();
@Input
public abstract Property<String> getType();
@Input
@Optional
public abstract Property<String> getEncoding();
@Input
@Optional
public abstract Property<Boolean> getVerbose();
@OutputDirectory
public abstract DirectoryProperty getOutputDirectory();
@Inject
protected abstract ExecOperations getExecOperations();
@TaskAction
public void precompileTemplates() {
getExecOperations().javaexec(javaexec -> {
javaexec.setClasspath(getClasspath());
javaexec.getMainClass().set("rife.template.TemplateDeployer");
List<String> 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);
});
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.provider.Property;
public abstract class Rife2Extension {
public abstract Property<String> getVersion();
public abstract Property<Boolean> getUseAgent();
public abstract Property<String> getUberMainClass();
}

View file

@ -0,0 +1,139 @@
/*
* 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.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;
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.TaskProvider;
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.stream.Collectors;
public class Rife2Plugin implements Plugin<Project> {
@Override
public void apply(Project project) {
PluginContainer plugins = project.getPlugins();
plugins.apply("java");
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
Rife2Extension rife2Extension = createRife2Extension(project, javaPluginExtension);
ConfigurationContainer configurations = project.getConfigurations();
DependencyHandler dependencyHandler = project.getDependencies();
Configuration rife2Configuration = createRife2Configuration(configurations, dependencyHandler, rife2Extension);
Configuration rife2CompilerClasspath = createRife2CompilerClasspathConfiguration(configurations, rife2Configuration);
Configuration rife2AgentClasspath = createRife2AgentConfiguration(configurations, dependencyHandler, rife2Extension);
configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration);
TaskProvider<PrecompileTemplates> precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath);
addTemplatesToMainOutput(precompileTemplates, javaPluginExtension);
configureAgent(project, plugins, rife2Extension, rife2AgentClasspath);
project.getTasks().register("uberJar", Jar.class, jar -> {
BasePluginExtension base = project.getExtensions().getByType(BasePluginExtension.class);
jar.getArchiveBaseName().convention(project.provider(() -> base.getArchivesName().get() + "-uber"));
jar.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
jar.into("webapp", spec -> spec.from("src/main/webapp"));
Configuration runtimeClasspath = project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
jar.from(javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput());
jar.from(runtimeClasspath.getElements().map(e -> e.stream().map(project::zipTree).collect(Collectors.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) {
CommandLineArgumentProvider agentProvider = () -> {
if (Boolean.TRUE.equals(rife2Extension.getUseAgent().get())) {
return Collections.singleton("-javaagent:" + rife2AgentClasspath.getAsPath());
}
return Collections.emptyList();
};
project.getTasks().named("test", Test.class, test -> test.getJvmArgumentProviders().add(agentProvider));
plugins.withId("application", unused -> project.getTasks().named("run", JavaExec.class, run -> run.getArgumentProviders().add(agentProvider)));
}
private static Rife2Extension createRife2Extension(Project project, JavaPluginExtension javaPluginExtension) {
Rife2Extension rife2 = project.getExtensions().create("rife2", Rife2Extension.class);
rife2.getUseAgent().convention(false);
rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass()
.map(mainClass -> mainClass + "Uber"));
return rife2;
}
private static Configuration createRife2CompilerClasspathConfiguration(ConfigurationContainer configurations, Configuration rife2Configuration) {
return configurations.create("rife2CompilerClasspath", conf -> {
conf.setDescription("The RIFE2 compiler classpath");
conf.setCanBeConsumed(false);
conf.setCanBeResolved(true);
conf.extendsFrom(rife2Configuration);
});
}
private static Configuration createRife2AgentConfiguration(ConfigurationContainer configurations,
DependencyHandler dependencyHandler,
Rife2Extension rife2Extension) {
return configurations.create("rife2Agent", conf -> {
conf.setDescription("The RIFE2 agent classpath");
conf.setCanBeConsumed(false);
conf.setCanBeResolved(true);
conf.setTransitive(false);
conf.getDependencies().addLater(rife2Extension.getVersion().map(version -> dependencyHandler.create("com.uwyn.rife2:rife2:" + version + ":agent")));
});
}
private static Configuration createRife2Configuration(ConfigurationContainer configurations,
DependencyHandler dependencyHandler,
Rife2Extension rife2Extension) {
Configuration config = configurations.create("rife2", conf -> {
conf.setDescription("The RIFE2 framework dependencies");
conf.setCanBeConsumed(false);
conf.setCanBeResolved(false);
});
config.getDependencies().addLater(rife2Extension.getVersion().map(version -> dependencyHandler.create("com.uwyn.rife2:rife2:" + version)));
return config;
}
private static void addTemplatesToMainOutput(TaskProvider<PrecompileTemplates> precompileTemplates,
JavaPluginExtension javaPluginExtension) {
javaPluginExtension.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
.getOutput()
.dir(precompileTemplates);
}
private static TaskProvider<PrecompileTemplates> registerPrecompileTemplateTask(Project project,
Configuration rife2CompilerClasspath) {
return project.getTasks().register("precompileTemplates", PrecompileTemplates.class, task -> {
task.getVerbose().convention(true);
task.getClasspath().from(rife2CompilerClasspath);
task.getType().convention("html");
task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir("src/main/templates"));
task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir("generated/classes/rife2"));
});
}
}

2
gradle.properties Normal file
View file

@ -0,0 +1,2 @@
org.gradle.parallel=true
org.gradle.caching=true

View file

@ -1,11 +1,6 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/7.6/userguide/multi_project_builds.html
*/
pluginManagement {
includeBuild("build-logic")
}
rootProject.name = "hello"
include("app","war")