Skip to content

Commit

Permalink
[Gradle] Initial version of Play Gradle Plugin
Browse files Browse the repository at this point in the history
 * Play Routes compiler plugin
 * Play Assets plugin
 * Play Run DEV-mode plugin
 * Support Play application layout
 * Helper function to add dependencies
  • Loading branch information
ihostage authored and mkurz committed Dec 7, 2023
1 parent aadd279 commit 9a9beb2
Show file tree
Hide file tree
Showing 21 changed files with 1,145 additions and 22 deletions.
1 change: 1 addition & 0 deletions dev-mode/gradle-plugin/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

# Ignore Gradle build output directory
build
bin
2 changes: 2 additions & 0 deletions dev-mode/gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import java.util.Properties
import kotlin.text.Charsets.UTF_8

plugins {
`kotlin-dsl`
alias(libs.plugins.gradle.plugin.publish)
alias(libs.plugins.nexus.publish)
alias(libs.plugins.spotless)
Expand All @@ -33,6 +34,7 @@ repositories {

dependencies {
compileOnly("org.playframework:play-routes-compiler_2.13:$playVersion")
implementation("org.playframework:play-run-support:$playVersion")
testImplementation(libs.assertj)
testImplementation(libs.commons.io)
testImplementation(libs.freemarker)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle;

import org.gradle.api.provider.Property;

public abstract class AssetsSettings {
public abstract Property<String> getPath();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle;

import org.gradle.api.Action;
import org.gradle.api.tasks.Nested;

/**
* The extension of the plugin allowing for configuring the target Scala version used for the
* application.
*/
public abstract class PlayExtension {

public static final String PLAY_EXTENSION_NAME = "play";

@Nested
public abstract RoutesSettings getRoutes();

public void routes(Action<? super RoutesSettings> action) {
action.execute(getRoutes());
}

@Nested
public abstract AssetsSettings getAssets();

public void assets(Action<? super AssetsSettings> action) {
action.execute(getAssets());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle;

import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;

public abstract class RoutesSettings {

public abstract Property<Boolean> getNamespaceReverseRouter();

public abstract Property<Boolean> getGenerateReverseRouter();

public abstract SetProperty<String> getImports();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle.internal;

import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.gradle.deployment.internal.Deployment;
import org.gradle.deployment.internal.DeploymentHandle;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.api.PlayException;
import play.runsupport.CompileResult;
import play.runsupport.CompileResult.CompileFailure;
import play.runsupport.CompileResult.CompileSuccess;
import play.runsupport.DevServer;
import play.runsupport.DevServerRunner;
import play.runsupport.classloader.AssetsClassLoader;

public class PlayRunHandle implements DeploymentHandle {

public static final Logger LOGGER = LoggerFactory.getLogger(PlayRunHandle.class);

private Deployment deployment;

private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final PlayRunParams params;

private DevServer devServer = null;

@Inject
public PlayRunHandle(PlayRunParams params) {
this.params = params;
}

@Override
public boolean isRunning() {
lock.readLock().lock();
try {
return devServer != null;
} finally {
lock.readLock().unlock();
}
}

/** Force reload Play application before next request. */
public void forceReload() {
lock.writeLock().lock();
try {
devServer.buildLink().forceReload();
} finally {
lock.writeLock().unlock();
}
}

private boolean isChanged() {
lock.readLock().lock();
try {
return deployment.status().hasChanged();
} finally {
lock.readLock().unlock();
}
}

private CompileResult reloadCompile() {
lock.readLock().lock();
try {
Throwable failure = deployment.status().getFailure();
if (failure != null) {
return new CompileFailure(
new PlayException("Gradle Build Failure", failure.getMessage(), failure));
}
return new CompileSuccess(Map.of(), params.getApplicationClasspath());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return new CompileFailure(new PlayException("Gradle Build Failure", e.getMessage(), e));
} finally {
lock.readLock().unlock();
}
}

private ClassLoader createAssetClassLoader(ClassLoader parent) {
return new AssetsClassLoader(
parent,
params.getAssetsDirectories().stream()
.map(file -> Map.entry(params.getAssetsPrefix() + "/", file))
.collect(Collectors.toList()));
}

@Override
public void start(@NotNull Deployment deployment) {
lock.writeLock().lock();
this.deployment = deployment;
try {
devServer =
DevServerRunner.startDevMode(
List.of(),
List.of(),
ClassLoader.getPlatformClassLoader(),
params.getDependencyClasspath(),
this::reloadCompile,
this::createAssetClassLoader,
this::isChanged,
List.of(),
null,
Map.of(),
params.getHttpPort(),
"0.0.0.0",
params.getProjectPath(),
Map.of(),
List.of(),
"play.core.server.DevServerStart",
lock);
} finally {
lock.writeLock().unlock();
}
}

@Override
public void stop() {
lock.writeLock().lock();
LOGGER.info("PlayApplication is stopping ...");
try {
devServer.close();
LOGGER.info("PlayApplication stopped.");
} catch (Exception e) {
LOGGER.error("PlayApplication stopped with exception", e);
} finally {
devServer = null;
deployment = null;
lock.writeLock().unlock();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle.internal;

import java.io.File;
import java.util.List;

public class PlayRunParams {
private final List<File> dependenciesClasspath;
private final List<File> applicationClasspath;
private final List<File> assetsDirectories;
private final String assetsPath;
private final File projectPath;
private final int httpPort;

public PlayRunParams(
List<File> dependenciesClasspath,
List<File> applicationClasspath,
List<File> assetsDirectories,
String assetsPath,
File projectPath,
int httpPort) {
this.dependenciesClasspath = dependenciesClasspath;
this.applicationClasspath = applicationClasspath;
this.assetsDirectories = assetsDirectories;
this.assetsPath = assetsPath;
this.projectPath = projectPath;
this.httpPort = httpPort;
}

public List<File> getDependencyClasspath() {
return dependenciesClasspath;
}

public List<File> getApplicationClasspath() {
return applicationClasspath;
}

public List<File> getAssetsDirectories() {
return assetsDirectories;
}

public File getProjectPath() {
return projectPath;
}

public int getHttpPort() {
return httpPort;
}

public String getAssetsPrefix() {
return assetsPath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle.internal;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.stream.Stream;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.work.ChangeType;
import org.gradle.workers.WorkAction;
import play.routes.compiler.RoutesCompiler$;
import play.routes.compiler.RoutesCompiler.GeneratedSource;
import play.routes.compiler.RoutesCompiler.GeneratedSource$;
import scala.Option;

/** Gradle work action that compile or delete one Routes file. */
public abstract class RoutesCompileAction implements WorkAction<RoutesCompileParams> {

private static final Logger LOGGER = Logging.getLogger(RoutesCompileAction.class);

@Override
public void execute() {
if (getParameters().getChangeType().get() == ChangeType.REMOVED) {
delete();
} else {
compile();
}
}

private boolean isGeneratedBy(File file, File origin) {
Option<GeneratedSource> generatedSource = GeneratedSource$.MODULE$.unapply(file);
if (generatedSource.isEmpty() || generatedSource.get().source().isEmpty()) return false;
return origin.equals(generatedSource.get().source().get());
}

private void delete() {
File routes = getParameters().getRoutesFile().getAsFile().get();
File destinationDirectory = getParameters().getDestinationDirectory().getAsFile().get();
try (Stream<Path> paths = Files.walk(destinationDirectory.toPath())) {
paths
.filter(Files::isRegularFile)
.filter(file -> isGeneratedBy(file.toFile(), routes))
.forEach(
file -> {
try {
Files.delete(file);
} catch (IOException e) {
LOGGER.warn("Deletion {} failed", file, e);
}
});
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}

private void compile() {
try {
File routes = getParameters().getRoutesFile().getAsFile().get();
File destinationDirectory = getParameters().getDestinationDirectory().getAsFile().get();
boolean namespaceReverseRouter = getParameters().getNamespaceReverseRouter().get();
boolean generateReverseRouter = getParameters().getGenerateReverseRouter().get();
Collection<String> imports = getParameters().getImports().get();
if (LOGGER.isInfoEnabled()) {
LOGGER.info(
"Compile Routes file {} into {}",
routes.getCanonicalPath(),
destinationDirectory.getCanonicalPath());
}
RoutesCompiler$.MODULE$.compile(
routes,
imports,
true,
generateReverseRouter,
namespaceReverseRouter,
destinationDirectory);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.gradle.internal;

import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.work.ChangeType;
import org.gradle.workers.WorkParameters;

/** Parameters of Routes compilation work action. */
public interface RoutesCompileParams extends WorkParameters {

Property<ChangeType> getChangeType();

RegularFileProperty getRoutesFile();

DirectoryProperty getDestinationDirectory();

Property<Boolean> getNamespaceReverseRouter();

Property<Boolean> getGenerateReverseRouter();

SetProperty<String> getImports();
}
Loading

0 comments on commit 9a9beb2

Please sign in to comment.