package org.gradle.playframework.plugins; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationPublications; import org.gradle.api.artifacts.ModuleVersionSelector; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.type.ArtifactTypeDefinition; import org.gradle.api.file.FileCollection; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.internal.artifacts.ArtifactAttributes; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.internal.file.FileCollectionInternal; import org.gradle.api.internal.file.collections.ImmutableFileCollection; import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection; import org.gradle.api.internal.tasks.TaskDependencyResolveContext; import org.gradle.api.plugins.scala.ScalaPlugin; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.playframework.extensions.PlayExtension; import org.gradle.playframework.extensions.PlayPlatform; import org.gradle.playframework.extensions.internal.PlayMajorVersion; import org.gradle.playframework.plugins.internal.PlayPluginHelper; import org.gradle.playframework.tasks.PlayRun; import org.gradle.playframework.tasks.RoutesCompile; import org.gradle.playframework.tasks.TwirlCompile; import org.gradle.util.VersionNumber; import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import static org.gradle.api.plugins.BasePlugin.ASSEMBLE_TASK_NAME; import static org.gradle.api.plugins.JavaPlugin.*; /** * Plugin for Play Framework component support. */ public class PlayApplicationPlugin implements Plugin<Project> { static final String PLAY_EXTENSION_NAME = "play"; static final String PLATFORM_CONFIGURATION = "play"; public static final String ASSETS_JAR_TASK_NAME = "createPlayAssetsJar"; @Override public void apply(Project project) { project.getPluginManager().apply(ScalaPlugin.class); PlayExtension playExtension = project.getExtensions().create(PLAY_EXTENSION_NAME, PlayExtension.class, project.getObjects()); Configuration playConfiguration = project.getConfigurations().create(PLATFORM_CONFIGURATION); project.getConfigurations().getByName(COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(playConfiguration); applyPlayPlugins(project); configureJavaAndScalaSourceSet(project); TaskProvider<Jar> mainJarTask = project.getTasks().named(JAR_TASK_NAME, Jar.class); TaskProvider<Jar> assetsJarTask = createAssetsJarTask(project); registerOutgoingArtifact(project, assetsJarTask); TaskProvider<PlayRun> playRun = createRunTask(project, playExtension, mainJarTask, assetsJarTask); project.afterEvaluate(project1 -> { PlayPlatform playPlatform = playExtension.getPlatform(); failIfInjectedRouterIsUsedWithOldVersion(playExtension.getInjectedRoutesGenerator().get(), playPlatform); addAutomaticDependencies(project.getDependencies(), playPlatform); configureRunTask(playRun, filtered(project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME))); }); } private void applyPlayPlugins(Project project) { project.getPluginManager().apply(PlayTwirlPlugin.class); project.getPluginManager().apply(PlayRoutesPlugin.class); } private void failIfInjectedRouterIsUsedWithOldVersion(Boolean injectedRoutesGenerator, PlayPlatform playPlatform) { if (Boolean.TRUE.equals(injectedRoutesGenerator)) { VersionNumber minSupportedVersion = VersionNumber.parse("2.4.0"); VersionNumber playVersion = VersionNumber.parse(playPlatform.getPlayVersion().get()); if (playVersion.compareTo(minSupportedVersion) < 0) { throw new GradleException("Injected routers are only supported in Play 2.4 or newer."); } } } private void addAutomaticDependencies(DependencyHandler dependencies, PlayPlatform playPlatform) { dependencies.add(PLATFORM_CONFIGURATION, playPlatform.getDependencyNotation("play").get()); dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION_NAME, playPlatform.getDependencyNotation("play-test").get()); dependencies.add(RUNTIME_CLASSPATH_CONFIGURATION_NAME, playPlatform.getDependencyNotation("play-docs").get()); PlayMajorVersion playMajorVersion = PlayMajorVersion.forPlatform(playPlatform); switch (playMajorVersion) { // This has the downside of adding play-java-forms for all kind of play projects // including Scala based projects. Still, users can exclude the dependency if they // want/need. Maybe in the future we can enable users to have some flag to specify // if the project is Java or Scala based. case PLAY_2_6_X: case PLAY_2_7_X: dependencies.add(PLATFORM_CONFIGURATION, playPlatform.getDependencyNotation("play-java-forms").get()); } } private void configureJavaAndScalaSourceSet(Project project) { SourceSet mainSourceSet = PlayPluginHelper.getMainJavaSourceSet(project); SourceDirectorySet mainResourcesDirectorySet = mainSourceSet.getResources(); mainResourcesDirectorySet.setSrcDirs(Arrays.asList("conf")); SourceDirectorySet mainScalaSourceDirectorySet = PlayPluginHelper.getMainScalaSourceDirectorySet(project); mainScalaSourceDirectorySet.setSrcDirs(Arrays.asList("app")); mainScalaSourceDirectorySet.include("**/*.scala", "**/*.java"); mainScalaSourceDirectorySet.srcDir(getTwirlCompileTask(project).flatMap(task -> task.getOutputDirectory())); mainScalaSourceDirectorySet.srcDir(getRoutesCompileTask(project).flatMap(task -> task.getOutputDirectory())); SourceDirectorySet testScalaSourceDirectorySet = PlayPluginHelper.getTestScalaSourceDirectorySet(project); testScalaSourceDirectorySet.setSrcDirs(Arrays.asList("test")); testScalaSourceDirectorySet.include("**/*.scala", "**/*.java"); } private TaskProvider<Jar> createAssetsJarTask(Project project) { TaskProvider<Jar> assetsJarTask = project.getTasks().register(ASSETS_JAR_TASK_NAME, Jar.class, jar -> { jar.setDescription("Assembles the assets jar for the application."); jar.setClassifier("assets"); jar.from(project.file("public"), copySpec -> copySpec.into("public")); }); project.getTasks().named(ASSEMBLE_TASK_NAME, assembleTask -> assembleTask.dependsOn(assetsJarTask)); return assetsJarTask; } private void registerOutgoingArtifact(Project project, TaskProvider<Jar> assetsJarTask) { Configuration runtimeElementsConfiguration = project.getConfigurations().getByName(RUNTIME_ELEMENTS_CONFIGURATION_NAME); PublishArtifact jarArtifact = new LazyPublishArtifact(assetsJarTask); ConfigurationPublications publications = runtimeElementsConfiguration.getOutgoing(); publications.getArtifacts().add(jarArtifact); publications.getAttributes().attribute(ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.JAR_TYPE); } private static TaskProvider<TwirlCompile> getTwirlCompileTask(Project project) { return project.getTasks().named(PlayTwirlPlugin.TWIRL_COMPILE_TASK_NAME, TwirlCompile.class); } private static TaskProvider<RoutesCompile> getRoutesCompileTask(Project project) { return project.getTasks().named(PlayRoutesPlugin.ROUTES_COMPILE_TASK_NAME, RoutesCompile.class); } private TaskProvider<PlayRun> createRunTask(Project project, PlayExtension playExtension, TaskProvider<Jar> mainJarTask, TaskProvider<Jar> assetsJarTask) { return project.getTasks().register("runPlay", PlayRun.class, playRun -> { playRun.setDescription("Runs the Play application for local development."); playRun.setGroup("Run"); playRun.getWorkingDir().convention(project.getLayout().getProjectDirectory()); playRun.getPlatform().convention(project.provider(() -> playExtension.getPlatform())); playRun.getApplicationJar().convention(mainJarTask.get().getArchiveFile()); playRun.getAssetsJar().convention(assetsJarTask.get().getArchiveFile()); playRun.getAssetsDirs().from(project.file("public")); }); } private void configureRunTask(TaskProvider<PlayRun> playRun, PlayConfiguration filteredRuntime) { playRun.configure(task -> { task.getRuntimeClasspath().from(filteredRuntime.getNonChangingArtifacts()); task.getChangingClasspath().from(filteredRuntime.getChangingArtifacts()); }); } PlayConfiguration filtered(Configuration configuration) { return new PlayConfiguration(configuration); } /** * Wrapper around a Configuration instance used by the PlayApplicationPlugin. */ class PlayConfiguration { private final Configuration configuration; PlayConfiguration(Configuration configuration) { this.configuration = configuration; } FileCollection getChangingArtifacts() { return new FilterByProjectComponentTypeFileCollection(configuration, true); } FileCollection getNonChangingArtifacts() { return new FilterByProjectComponentTypeFileCollection(configuration, false); } } private static class FilterByProjectComponentTypeFileCollection extends LazilyInitializedFileCollection { private final Configuration configuration; private final boolean matchProjectComponents; private FilterByProjectComponentTypeFileCollection(Configuration configuration, boolean matchProjectComponents) { this.configuration = configuration; this.matchProjectComponents = matchProjectComponents; } @Override public String getDisplayName() { return configuration.toString(); } @Override public FileCollectionInternal createDelegate() { Set<File> files = new HashSet<>(); for (ResolvedArtifact artifact : configuration.getResolvedConfiguration().getResolvedArtifacts()) { if ((artifact.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier) == matchProjectComponents) { files.add(artifact.getFile()); } } return ImmutableFileCollection.of(Collections.unmodifiableSet(files)); } @Override public void visitDependencies(TaskDependencyResolveContext context) { context.add(configuration); } } }