/*
* Copyright 2014 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
*
* http://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 org.gradle.play.plugins;
import org.apache.commons.lang.StringUtils;
import org.gradle.api.*;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ResolvableDependencies;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.project.ProjectIdentifier;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.plugins.scala.ScalaBasePlugin;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.scala.IncrementalCompileOptions;
import org.gradle.api.tasks.scala.ScalaCompile;
import org.gradle.jvm.platform.internal.DefaultJavaPlatform;
import org.gradle.model.Mutate;
import org.gradle.model.Path;
import org.gradle.model.RuleSource;
import org.gradle.model.collection.CollectionBuilder;
import org.gradle.platform.base.*;
import org.gradle.play.PlayApplicationBinarySpec;
import org.gradle.play.PlayApplicationSpec;
import org.gradle.play.internal.DefaultPlayApplicationBinarySpec;
import org.gradle.play.internal.DefaultPlayApplicationSpec;
import org.gradle.play.internal.DefaultPlayToolChain;
import org.gradle.play.internal.PlayApplicationBinarySpecInternal;
import org.gradle.play.tasks.RoutesCompile;
import org.gradle.play.tasks.TwirlCompile;
import org.gradle.util.WrapUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.Callable;
/**
* Plugin for Play Framework component support.
* Registers the {@link org.gradle.play.PlayApplicationSpec} component type for
* the {@link org.gradle.platform.base.ComponentSpecContainer}.
*/
@Incubating
public class PlayApplicationPlugin implements Plugin<ProjectInternal> {
public static final String DEFAULT_SCALA_BINARY_VERSION = "2.10";
public static final String DEFAULT_PLAY_VERSION = "2.3.5";
public static final String DEFAULT_PLAY_ID = DEFAULT_SCALA_BINARY_VERSION+"-"+DEFAULT_PLAY_VERSION;
public static final String PLAY_GROUP = "com.typesafe.play";
public static final String DEFAULT_PLAY_DEPENDENCY = PLAY_GROUP+":play_"+DEFAULT_SCALA_BINARY_VERSION+":"+DEFAULT_PLAY_VERSION;
public static final String DEFAULT_TWIRL_DEPENDENCY = PLAY_GROUP+":twirl-compiler_"+DEFAULT_SCALA_BINARY_VERSION+":1.0.2";
public static final String TWIRL_CONFIGURATION_NAME = "twirl";
public static final String PLAYAPP_COMPILE_CONFIGURATION_NAME = "playAppCompile";
public static final String PLAYAPP_RUNTIME_CONFIGURATION_NAME = "playAppRuntime";
public static final String PLAY_ROUTES_DEPENDENCY_NAME = "routes-compiler";
public static final String DEFAULT_PLAY_ROUTES_DEPENDENCY = PLAY_GROUP+":"+PLAY_ROUTES_DEPENDENCY_NAME+"_"+DEFAULT_SCALA_BINARY_VERSION+":"+DEFAULT_PLAY_VERSION;
public static final String PLAY_ROUTES_CONFIGURATION_NAME = "playRoutes";
public static final String PLAY_MAIN_CLASS = "play.core.server.NettyServer";
private ProjectInternal project;
public void apply(final ProjectInternal project) {
project.apply(WrapUtil.toMap("type", ScalaBasePlugin.class));
this.project = project;
setupTwirlCompilation();
setupRoutesCompilation();
setupPlayAppClasspath();
}
private void setupPlayAppClasspath() {
final Configuration playAppCompileClasspath = createConfigurationWithDefaultDependency(PLAYAPP_COMPILE_CONFIGURATION_NAME, DEFAULT_PLAY_DEPENDENCY);
playAppCompileClasspath.setDescription("The dependencies to be used for Scala compilation of a Play application.");
project.getTasks().withType(ScalaCompile.class).all(new Action<ScalaCompile>(){
public void execute(ScalaCompile scalaCompile) {
scalaCompile.getConventionMapping().map("classpath", new Callable<FileCollection>() {
public FileCollection call() throws Exception {
return project.getConfigurations().getByName(PLAYAPP_COMPILE_CONFIGURATION_NAME);
}
});
}
});
final Configuration playAppRuntimeClasspath = project.getConfigurations().create(PLAYAPP_RUNTIME_CONFIGURATION_NAME);
playAppRuntimeClasspath.extendsFrom(playAppCompileClasspath);
}
private void setupTwirlCompilation() {
Configuration twirlConfiguration = createConfigurationWithDefaultDependency(TWIRL_CONFIGURATION_NAME, DEFAULT_TWIRL_DEPENDENCY);
twirlConfiguration.setDescription("The dependencies to be used Play Twirl template compilation.");
project.getTasks().withType(TwirlCompile.class).all(new Action<TwirlCompile>(){
public void execute(TwirlCompile twirlCompile) {
twirlCompile.getConventionMapping().map("compilerClasspath", new Callable<FileCollection>() {
public FileCollection call() throws Exception {
return project.getConfigurations().getByName(TWIRL_CONFIGURATION_NAME);
}
});
}
});
}
private static String detectRoutesCompilerVersion(Configuration routesConfiguration) {
String routesCompilerVersion = null;
for (Dependency dep: routesConfiguration.getDependencies()) {
if (dep.getGroup().equals(PlayApplicationPlugin.PLAY_GROUP) && dep.getName().startsWith(PlayApplicationPlugin.PLAY_ROUTES_DEPENDENCY_NAME)){
routesCompilerVersion = dep.getVersion();
break;
}
}
if (routesCompilerVersion == null) {
throw new InvalidUserDataException("Could not find a routes compiler version for in dependencies: " + routesConfiguration.getDependencies());
}
return routesCompilerVersion;
}
private void setupRoutesCompilation() {
Configuration routesConfiguration = createConfigurationWithDefaultDependency(PLAY_ROUTES_CONFIGURATION_NAME, DEFAULT_PLAY_ROUTES_DEPENDENCY);
routesConfiguration.setVisible(false);
routesConfiguration.setDescription("The dependencies to be used Play Routes compilation.");
project.getTasks().withType(RoutesCompile.class).all(new Action<RoutesCompile>(){
public void execute(RoutesCompile routesCompile) {
final Configuration routesConfiguration = project.getConfigurations().getByName(PLAY_ROUTES_CONFIGURATION_NAME);
routesCompile.getConventionMapping().map("routesCompilerVersion", new Callable<String>() {
public String call() throws Exception {
return detectRoutesCompilerVersion(routesConfiguration);
}
});
routesCompile.getConventionMapping().map("compilerClasspath", new Callable<FileCollection>() {
public FileCollection call() throws Exception {
return routesConfiguration;
}
});
}
});
}
private Configuration createConfigurationWithDefaultDependency(String configurationName, final String defaultDependency) {
final Configuration configuration = project.getConfigurations().create(configurationName);
configuration.setVisible(false);
configuration.getIncoming().beforeResolve(new Action<ResolvableDependencies>() {
public void execute(ResolvableDependencies resolvableDependencies) {
DependencySet dependencies = configuration.getDependencies();
if (dependencies.isEmpty()) {
dependencies.add(project.getDependencies().create(defaultDependency));
}
}
});
return configuration;
}
/**
* Model rules.
*/
@SuppressWarnings("UnusedDeclaration")
@RuleSource
static class Rules {
@ComponentType
void register(ComponentTypeBuilder<PlayApplicationSpec> builder) {
builder.defaultImplementation(DefaultPlayApplicationSpec.class);
}
@BinaryType
void registerApplication(BinaryTypeBuilder<PlayApplicationBinarySpec> builder) {
builder.defaultImplementation(DefaultPlayApplicationBinarySpec.class);
}
@ComponentBinaries
void createBinaries(CollectionBuilder<PlayApplicationBinarySpec> binaries, final PlayApplicationSpec componentSpec, @Path("buildDir") final File buildDir) {
binaries.create(String.format("%sBinary", componentSpec.getName()), new Action<PlayApplicationBinarySpec>() {
public void execute(PlayApplicationBinarySpec playBinary) {
PlayApplicationBinarySpecInternal playBinaryInternal = (PlayApplicationBinarySpecInternal) playBinary;
JavaVersion currentJava = JavaVersion.current();
playBinaryInternal.setTargetPlatform(new DefaultJavaPlatform(currentJava));
playBinaryInternal.setToolChain(new DefaultPlayToolChain(DEFAULT_PLAY_ID, currentJava));
playBinaryInternal.setJarFile(new File(buildDir, String.format("jars/%s/%s.jar", componentSpec.getName(), playBinaryInternal.getName())));
}
});
}
@BinaryTasks
void createPlayApplicationTasks(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary, final ProjectIdentifier projectIdentifier, @Path("buildDir") final File buildDir) {
final String twirlCompileTaskName = String.format("twirlCompile%s", StringUtils.capitalize(binary.getName()));
final File twirlCompilerOutputDirectory = new File(buildDir, String.format("%s/twirl", binary.getName()));
final File routesCompilerOutputDirectory = new File(buildDir, String.format("%s/src_managed", binary.getName()));
tasks.create(twirlCompileTaskName, TwirlCompile.class, new Action<TwirlCompile>() {
public void execute(TwirlCompile twirlCompile) {
twirlCompile.setOutputDirectory(new File(twirlCompilerOutputDirectory, "views"));
twirlCompile.setSourceDirectory(new File(projectIdentifier.getProjectDir(), "app"));
twirlCompile.setSource(twirlCompile.getSourceDirectory());
twirlCompile.include("**/*.html");
binary.builtBy(twirlCompile);
}
});
final String routesCompileTaskName = String.format("routesCompile%s", StringUtils.capitalize(binary.getName()));
tasks.create(routesCompileTaskName, RoutesCompile.class, new Action<RoutesCompile>() {
public void execute(RoutesCompile routesCompile) {
routesCompile.setOutputDirectory(routesCompilerOutputDirectory);
routesCompile.setAdditionalImports(new ArrayList<String>());
routesCompile.setSource(new File(projectIdentifier.getProjectDir(), "conf/routes"));
binary.builtBy(routesCompile);
}
});
final String scalaCompileTaskName = String.format("scalaCompile%s", StringUtils.capitalize(binary.getName()));
final File compileOutputDirectory = new File(buildDir, String.format("classes/%s/app", binary.getName()));
tasks.create(scalaCompileTaskName, ScalaCompile.class, new Action<ScalaCompile>() {
public void execute(ScalaCompile scalaCompile) {
scalaCompile.setDestinationDir(compileOutputDirectory);
scalaCompile.setSource("app");
IncrementalCompileOptions incrementalOptions = scalaCompile.getScalaCompileOptions().getIncrementalOptions();
incrementalOptions.setAnalysisFile(new File(buildDir, String.format("tmp/scala/compilerAnalysis/%s.analysis", scalaCompileTaskName)));
// /ignore uncompiled twirl templates
scalaCompile.exclude("**/*.html");
//handle twirl compiler output
scalaCompile.dependsOn(twirlCompileTaskName);
//handle routes compiler
scalaCompile.dependsOn(routesCompileTaskName);
scalaCompile.source(twirlCompilerOutputDirectory);
scalaCompile.source(routesCompilerOutputDirectory);
}
});
String jarTaskName = String.format("create%sJar", StringUtils.capitalize(binary.getName()));
tasks.create(jarTaskName, Jar.class, new Action<Jar>() {
public void execute(Jar jar) {
jar.setDestinationDir(binary.getJarFile().getParentFile());
jar.setArchiveName(binary.getJarFile().getName());
jar.from(compileOutputDirectory);
jar.from("public");
jar.from("conf");
// CollectionBuilder api currently does not allow autowiring for tasks
jar.dependsOn(scalaCompileTaskName);
}
});
}
@Mutate
void createPlayApplicationTasks(CollectionBuilder<Task> tasks, BinaryContainer binaryContainer) {
for(final PlayApplicationBinarySpec binary : binaryContainer.withType(PlayApplicationBinarySpec.class)){
String runTaskName = String.format("run%s", StringUtils.capitalize(binary.getName()));
tasks.create(runTaskName, JavaExec.class, new Action<JavaExec>() {
public void execute(JavaExec javaExec) {
javaExec.dependsOn(binary.getBuildTask());
javaExec.setMain(PLAY_MAIN_CLASS);
Project project = javaExec.getProject();
FileCollection classpath = project.files(binary.getJarFile()).plus(project.getConfigurations().getByName(PLAYAPP_RUNTIME_CONFIGURATION_NAME));
javaExec.setClasspath(classpath);
}
});
}
}
}
}