This specification outlines the work that is required to use Gradle to build applications that use the Play framework.
There are 3 main use cases:
- A developer builds a Play application.
- A developer runs a Play application during development.
- A deployer runs a Play application. That is, a Play application is packaged up as a distribution which can be run in a production environment.
The following features are currently out of scope for this spec, but certainly make sense for later work:
- Building a Play application for multiple Scala versions. For now, the build for a given Play application will target a single Scala version. It will be possible to declare which version of Scala to build for.
- Using continuous mode with JVMs older than Java 7. For now, this will work only with Java 7 and later will be supported. It will be possible to build and run for Java 6.
- Any specific IDE integration, beyond Gradle's current general purpose IDE integration for Java and Scala.
- Any specific testing support, beyond Gradle's current support for testing Java and Scala projects.
- Any specific support for publishing and resolving Play applications, beyond Gradle's current general purpose capabilities.
- Any specific support for authoring plugins, beyond Gradle's current support.
- Installing the Play tools on the build machine.
- Migrating or importing SBT settings for a Play project.
Performance should be comparable to SBT:
- Building and starting an application.
- Reload after a change.
- Executing tests for an application.
For the first milestone, a developer will be able to define, build and run a simple play application that consists of routes and templates, Java and Scala sources, as well as Javascript, CSS, CoffeeScript and LESSCSS sources.
In this milestone we are not dealing with:
- Automatically rebuilding the play application when sources change
- 3rd party dependencies for Java, Scala or Javascript. (This may change).
The goal is to get something working pretty quickly, and work to improve the configurability and discoverability over time.
Add a play-application
plugin that provides Play application component:
plugins {
id 'play-application'
}
model {
components {
myapp(PlayApplicationSpec)
}
}
Runninggradle assemble
builds an empty Jar file.Runninggradle components
shows some basic details about the Play application.- In this story, no source files are supported.
component report shows PlayApplicationSpec withversion info about-play (declared in the plugin)-java (picked current version for now)assemble creates an empty jar file
When running gradle assemble
, a Jar file will be built for the default template application generated by Play.
- Using hard-coded:
- Locations of java/scala files
- Locations of routes file
- Locations of templates files
- Scala version
- Dependencies of Play ("com.typesafe.play:play_2.11:2.3.7")
- Dependency of template compiler ("com.typesafe.play:twirl-compiler_2.11:1.0.2)
- Dependency of routes compiler
- resolve different play dependencies from user-configured repositories
- setup TwirlCompiler task type
- setup RoutesCompiler task type
- Compile routes to scala and java
- Compile templates to scala
- Compile all scala (app/*/*.{scala,java}, output of: conf/routes, output of: app/views/*.scala.html) files
- Output class files are part of assemble jar file
- verify that generated scala template files exists
- verify that generated scala/java route files exists
gradle assemble
should trigger compile task and output jar should contain class files
Extend the Play support to allow the Play application to be executed.
- Running
gradle assemble
produces an executable Jar that, when executed, runs the Play application. - Running
gradle run<ComponentName>
builds and executes the Play application.
At this stage, only the default generated Play application is supported, with a hard-coded version of Scala and Play.
- verify that play app can be built and executed with play version 2.3.7 and 2.2.3
- Can configure port for launched PlayApp: default is 9000
- Stopping the app with Ctrl-D?
- Testing stopping via keyboard interaction with Daemon (currently test is disabled)
- Add a
Test
test task per binary that runs the play application tests based on JUnit TestRunner against the binary - The test sources are compiled against the binary and the
play-test
dependency based on play and scala version of the platform - Can execute Play unit and integration tests
- Fails with a nice error message
- Wired into the
check
andtest
lifecycle tasks
- Verify that running
gradle testBinary
andgradle test
executes tests in the/test
directory - Verify output and reports generated for successful tests
- Verify output and reports generated for failing tests
- Model test suites
- Pull
check
lifecycle task up intoLifecycleBasePlugin
and wire in all test tasks.
- Can build play application with Play 2.2.3 on Scala 2.10, and Play 2.3.7 on Scala 2.11
- Verify building and running the 'activator new' app with Play 2.2.3 and Play 2.3.7
- Compile Scala wrappers around different Play versions, and load these via reflection
Introduce some lifecycle tasks to allow the developer to package up the Play application. For example, the
developer may run gradle stage
to stage the local application, or gradle dist
to create a standalone distribution.
- Build distribution image and zips, as per
play stage
andplay dist
- Default distributions are added for each play binary
- Developer can add additional content to a default distribution:
model {
distributions {
playBinary {
contents {
from "docs"
}
}
}
}
- Default distributions are added for each play binary
- Stage and Zip tasks are added for each distribution
- Lifecycle tasks for "stage" and "dist" are added to aggregate all distribution tasks
- Distribution base name defaults to distribution name, which defaults to play binary name
- Distribution archive name defaults to "[distribution-base-name]-[project.version].zip"
- Public assets are packaged in a separate jar with a classifier of "assets".
- Default distribution zip contains:
- all content under a directory named "[distribution-base-name]-[project.version]"
- jar and assets jar in "lib".
- all runtime libraries in "lib".
- application script and batch file in "bin".
- application.conf and any secondary routes files in "conf".
- any README file provided in conventional location "${projectDir}/README"
- content added to the distribution is also included in the zip
- application script and batch file will successfully run play:
- can access a public asset
- can access a custom route
- A Play distribution zip, by default, contains a shared/docs directory with the scaladocs for the application. We'll need a scaladoc task wired in to duplicate this functionality.
Add a new 'scala-lang' plugin to add Scala language support implemented in the same manner as the JavaLanguagePlugin.
plugins {
id 'jvm-component'
id 'scala-lang'
}
model {
components {
myLib(JvmLibrarySpec)
myOtherLib(JvmLibrarySpec) {
sources {
scala {
source.srcDir "src/myOtherLib/myScala"
}
otherScala(ScalaSourceSet) {
source.srcDir "src/otherScala"
}
}
}
}
}
- Scala source set(s) and locations are visible in the components report
- Uses scala sources from conventional location
- Can configure location of scala sources
- Build is incremental:
- Changed in Scala comment does not result in rebuilding JVM library
- Change in Scala compile properties triggers recompilation
- Change in Scala platform triggers recompilation
- Change in Scala ToolChain triggers recompilation
- Removal of Scala source files removes classes for that source file, which are removed from JVM library
- Compile is incremental: consider sources A, B, C where B depends on C
- Change A, only A is recompiled
- Change B, only B is recompiled
- Change C, only B & C are recompiled
- Remove A, outputs for A are removed
- Can include both Java and Scala sources in lib: no cross-compilation
Extend the Play support to full model Scala sources and Jvm resources.
- A default ScalaLanguageSourceSet 'scala' will include java and scala source files in
app/controllers
andapp/models
. - A default JvmResourceSet 'resources' will include files in
public
andconf
- Files can be included/excluded in the default source sets
- Additional source sets can be configured
- All source sets are listed in the component report for a play application
- All Scala source sets in a Play application are joint-compiled
plugins {
id 'play-application'
}
model {
components {
play(PlayApplicationSpec) {
sources {
scala {
source.include "extraStuff/**"
}
resources {
source.srcDir "src/assets"
}
extraScala(ScalaLanguageSourceSet) {
source.srcDir "src/extraScala"
}
}
}
}
}
- Scala and Jvm source sets (and locations) are visible in the components report
- Can configure additional includes and/or source directories for default
scala
andresources
source sets - Can provide an additional Scala source set for a Play application, with dependencies on main sources
- Can provide an additional resources set for a Play application
- Build is incremental:
- Changed in Scala comment does not result in rebuilding Play app
- Change in Scala or Java source file triggers recompilation
- Change in Scala or Java compile properties triggers recompilation
- Change in Platform triggers recompilation
- Change in ToolChain triggers recompilation
- Removal of Scala/Java source files removes classes from the Play app
- Use
LanguageTransform
fromscala-lang
plugin to configure scala compile task for source sets - Use source set dependencies to determine of source sets should be joint compiled
Add a TwirlSourceSet and permit multiple instances in a Play application
- Twirl sources show up in component report
- Allow Twirl source location to be configured
- Allow additional Twirl source sets to be configured
- Define a generated ScalaSourceSet as the output for each TwirlSourceSet compilation
- Generated ScalaSourceSet will be one of the Scala compile inputs
- Generated ScalaSourceSet should not be visible in components report
- Source generation and compilation should be incremental and remove stale outputs.
- handle non html templates
- Ability for Twirl compiler to prefer Java types in generated sources (i.e. SBT enablePlugins(PlayJava))
- Verify that default imports are configured: https://github.com/playframework/playframework/blob/master/framework/src/build-link/src/main/java/play/TemplateImports.java
- Allow Twirl to be used in a non-play project
Add a RoutesSourceSet and permit multiple instances in a Play application
- Routes sources show up in component report
- Allow Routes source location to be configured
- Allow additional Routes source sets to be configured
- Define a generated ScalaSourceSet as the output for each RoutesSourceSet compilation
- Generated ScalaSourceSet will be one of the Scala compile inputs
- Generated ScalaSourceSet should not be visible in components report
- Source generation and compilation should be incremental and remove stale outputs.
- handle .routes files
- Ability for Routes compiler to prefer Java types in generated sources (i.e. SBT enablePlugins(PlayJava))
Any files under /public
in the application sources are available under /assets
in the running application.
Add a coffee script plugin as well as JavaScriptSourceSet and CoffeeScriptSourceSets and permit multiple instances.
plugins {
id 'play-application'
id 'play-coffeescript'
}
model {
components {
play(PlayApplicationSpec) {
sources {
extraCoffeeScript(CoffeeScriptSourceSet) {
sources.srcDir "src/extraCoffeeScript"
}
extraJavaScript(JavaScriptSourceSet) {
sources.srcDir "src/extraJavaScript"
}
}
}
}
}
- Default coffeescript sourceset should be "app/assets/**/*.coffee"
- Compiled coffeescript files will be added to the jar under "public"
- Default javascript sourceset should be "app/assets/**/*.js"
- Processed javascript sourceset should be added to the jar under "public"
- Coffeescript and javascript sources are visible in the components report
- Coffeescript sources successfully compiled to javascript
- Compiled coffeescript is added to jar under "public"
- Javascript sources are copied directly into jar under "public"
- Can provide additional coffeescript sources
- Can provide additional javascript sources
- Build is incremental:
- Change in coffeescript source triggers recompile
- No change in coffeescript source does not trigger a recompile
- Removal of generated javascript triggers recompile
- Removal of coffeescript source files removes generated javascript
Extend the basic JavaScript plugin to include minified javascript assets in the Play application.
Use the Google Closure Compiler to produce a minified version of all Javascript assets. The minified files
should be named <original-name>.min.<original-extension>
(usually the extension will be .js
).
plugins {
id 'play'
}
model {
components {
play(PlayApplicationSpec) {
sources {
extraJavaScript(JavaScriptSourceSet) {
sources.srcDir "src/extraJavaScript"
}
}
}
}
}
- Any javascript file in
app/assets
is available in minified form in the app. - Any javascript file in a configured JavaScriptSourceSet is available in minified form in the app.
- Any compiled coffeeScript source file is available in both non-minified and minified javascript forms.
- Build is incremental:
- Minifier is not executed when no source inputs have changed
- Changed javascript source produces changed minified javasript
- Changed coffeescript source produces changed minified javascript
- Removal of javascript source removes minified javascript
- Removal of coffeescript source removes both minified and non-minified javascript
Before the support for Play framework is fully usable and can be properly 'released' we need to add documentation and release notes.
model {
components {
play(PlayApplicationSpec) {
platform play: "2.3.7", scala: "2.11"
}
}
}
- If not specified, Play major version number implies Scala version
- Play 2.2.x -> Scala 2.10
- Play 2.3.x -> Scala 2.11
- Java version is taken from version executing Gradle
- For each supported Play version: 2.2.3, 2.3.7
- Can assemble Play application
- Can run Play application
- Can test Play application
- Can build & test multiple Play application variants in single build invocation
gradle assemble
builds all variantsgradle test
tests all variants
- Play version should be visible in components report and dependencies reports.
- Most Play integration tests should be able to run against different supported platforms
- Developer build should run against single version by default
- CI should run against all supported versions (using '-PtestAllPlatforms=true')
model {
components {
play(PlayApplicationSpec) {
platform play: "2.3.7", scala: "2.11", java: "1.8"
}
}
}
plugins {
id 'jvm-component'
id 'scala-lang'
}
model {
components {
scalaLib(JvmLibrarySpec) {
platform java: "1.6", scala: "2.10"
}
}
}
- Pull
platform(Object)
method up fromPlayApplicationSpec
toPlatformAwareComponentSpec
- Allow arbitrary
Map<String, String>
inputs toplatform()
, converting into aPlatformRequirement
- Add a
CompositePlatform
type that is returned byPlatformResolver.resolve()
- Allow various typed
PlatformResolver
instances to plugin to the compositePlatformResolver
- When resolving a play platform, return a composite platform with
PlayPlatform
,ScalaPlatform
andJavaPlatform
components. - When resolving a jvm platform, return a composite platform with
JavaPlatform
and optionalScalaPlatform
components.- Maybe add a
JvmPlatform
that extendsCompositePlatform
, encoding jvm compatibility version. - Maybe make
PlayPlatform
extendCompositePlatform
with aJvmPlatform
component. Later will have aBrowserPlatform
component.
- Maybe add a
- Allow various typed
- Scala-lang plugin registers a resolver that returns a
ScalaPlatform
for a 'scala' platform requirement
model {
components {
nativeLib(NativeLibrarySpec) {
platform os: "windows", arch: "x86"
}
}
}
This story adds a general-purpose mechanism which is able to keep the output of some tasks up-to-date when source files change.
For example, a developer may run gradle --watch <tasks>
.
When run in continuous mode, Gradle will execute a build and determine any files that are inputs to that build. Gradle will then watch for changes to those input files, and re-execute the build when any file changes.
Input files are determined as:
- Files that are inputs to a task but not outputs of some other task
- Files that are inputs to the model
So:
gradle --watch run
would build and run the Play application. When a change to the source files are detected, Gradle would rebuild and restart the application.gradle --watch test run
would build and run the tests and then the Play application. When a change to the source files is detected, Gradle would rerun the tests, rebuild and restart the Play application.gradle --watch test
would build and run the tests. When a source file changes, Gradle would rerun the tests.
Note that for this feature, the implementation will assume that any source file affects the output of every task listed on the command-line.
For example, running gradle --watch test run
would restart the application if a test source file changes.
Gradle will be able to start, run a set of tasks and then wait for a retrigger before re-executing the build.
See spike: https://github.com/lhotari/gradle/commit/969510762afd39c5890398e881a4f386ecc62d75
- Gradle CLI/client process connects to a daemon process as normal.
- Gradle Daemon knows when we're in "continuous mode" and repeats the last build until cancelled.
- Gradle CLI waits for the build to finish (as normal).
- Instead of returning after each build, the daemon goes into a retry loop until cancelled triggered by something.
- Initial implementation will use a periodic timer to trigger the build.
- Add new command-line option (
--watch
)- Add a separate Parameters option
- Think about how to introduce a new internal replacement for StartParameter
- Decorator for
InProcessBuildExecutor
changes to understand "continuous mode"- Similar to what the spike did
- Build loop delegates to wrapped BuildActionExecuter
- After build, executor waits for a trigger from somewhere else
- On Ctrl+C, Gradle exits and cancels build.
pseudo:
interface TriggerDetails {
String reason
}
interface TriggerListener {
void triggered(TriggerDetails)
}
interface Triggerable {
TriggerDetails waitForTrigger()
}
// In run/execute()
while (not cancelled) {
delegateExecuter.execute(...)
triggerable.waitForTrigger()
}
// Triggerable
def waitForTrigger() {
sync(lock) {
while(!triggered) {
lock.wait()
}
}
}
def triggered(TriggerDetails) {
sync(lock) {
lock.notify()
}
}
If Gradle build succeeds, we wait for trigger and print some sort of helpful message.If Gradle build fails, we still wait for trigger.Configuration errors should be treated in the same way as execution failures.When "trigger" is tripped, a build runs.Add coverage for a build that succeeds, then fails, then succeeds (eg a compilation error)Fail when this is enabled on Java 6 builds, tell the user this is only supported for Java 7+.
Gradle will be able to start, run a set of tasks and then monitor one file for changes without exiting. When this file is changed, the same set of tasks will be re-run.
- Watch project directory (
projectDir
) for changes to trigger re-run - Add
FileWatchService
that can be given Files to watch - When files change, mark the file as out of date
- Re-run trigger polls the watch service for changes at some default rate ("quiet period")
Ignore build/ .gradle/ etc files.
When the project directory files change/are create/are delete, Gradle re-runs using the same set of task selectors.
N/A
After performing a build, Gradle will automatically rerun the same logical build if the file system inputs of any task that was executed change.
- A logical description of the input files for each task that is executed is captured during the build
- At the end of the build, Gradle will monitor the file system for changes
- If any change occurs to a file/directory described by #1, the build will execute again after shutting down all file system listeners
Constraints:
- There is no way to stop listening for filesystem changes other than use of ctrl-c or similar
- Changes that after the task executes, but before the build completes and starts watching the file system, are “ignored” (i.e. do not trigger a rebuild)
- Builds are triggered as soon as a change to a relevant file is noticed (i.e. no quiet period)
- Continuous mode is not supported by Tooling API (build fails eagerly as unsupported)
- Symlinks are treated as regular files and changes “behind” symlinks are not respected
- Only explicit file system inputs are respected (i.e. no consideration given to build logic changes)
- Changes to
buildSrc
are not respected - Only changes to inputs of task that were executed in the immediately preceding build are respected (
A.dependsOn(B.dependsOn(C))
- if C fails, changes to inputs ofA
andB
are not respected)
Reasonable feedback when using continuous mode (e.g. incubating message, suitable message at end of build)- If build fails before any task executes, build exits and does not enter continuous mode
Can trigger rebuild by changing input file to simple task (i.e. basic smoke test)Continuous mode utilises class reuse (e.g. same build script class instance is used for subsequent builds)Change to non source input file of task with empty source does not trigger build
For a conventional apply plugin: 'java'
project:
Can successfully build in continuous mode when there is no source (i.e.src/main/java
does not exist)Can successfully build in continuous after source dirsrc/main/java
is removedAddition of empty directories tosrc/main/java
does not trigger rebuild (i.e. source is defined asinclude("**/*.java")
)After compile failure of Java file, correcting file to be compilable triggers a successful buildWhen runningtest
in continuous mode:Change to source file causes execution of testsChange to test file causes execution of testsChange to resource file (src/main/resources
)
Change to local filesystem compile dependency triggers rebuild (e.g.lib/some.jar
, not a repo dependency)Remove of a local filesystem compile dependency triggers rebuildIn a multi project, changes to Java source of upstream projects trigger the build of downstream projectsProject that utilises external repository dependencies can be built in continuous mode (i.e. exercise dependency management layers)When main source fails to compile, changes to test source does not trigger buildCreation of initial source file after initial “empty” build triggers building of jar (i.e. add/src/main/java/Thing.java
)Addition of a local filesystem compile dependency triggers rebuild (e.g.dependencies { compile fileTree("lib") }
)
Continuous mode can be used successfully on a project with abuildSrc
directoryAttempting to run a continuous mode build from the Tooling API yields an error immediately
Failure to determine file system inputs for tasks yields reasonable error message (e.g.javaCompile.src(files( { throw new Exception("!") }))
)Task can specify project directory as a task input; changes are respectedTask can specify root directory of multi project build as a task input; changes are respectedContinuous mode can be used on reasonable size multi project Java build in conjunction with --parallelCan use a symlink as an input fileSymlinks are not followed for watching purposes (i.e. contents of symlinked directory are not watched)
With zip task whose contents are directorysrc
, adding a new empty directory causes rebuildChanges to input zips are respectedChanges to input tars are respected (compressed and uncompressed)
This story adds support for executing continuous builds via the Tooling API. It does not address continually building/providing tooling models. It also does not improve the general capabilities of continuous mode.
- client executes continuous build that succeeds, then responds to input changes and succeeds
- client executes continuous build that succeeds, then responds to input changes and fails, then … and succeeds
- client executes continuous build that fails, then responds to input changes and succeeds
- client can cancel during execution of a continuous build
- client can cancel while a continuous build is waiting for changes
- client can request continuous mode when building a model, but request is effectively ignored
- client can receive appropriate logging and progress events for subsequent builds in continuous mode
- client receives appropriate error if continuous mode attempted on unsupported platform
- logging does not include message to use
ctrl-c
to exit continuous mode
Prior to this story, the only way for a command line user to exit continuous mode is to kill the process. This story makes the use of continuous mode more effective by allowing better utilisation of warm Gradle daemons.
ctrl-d
will replace ctrl-c
as the advertised mechanism for escaping wait state when using continuous build.
- Performance benchmarking
- Responding the changes that occur during the build that affect inputs of already executed tasks
- Responding to changes to build logic implementation:
buildSrc
- project build scripts
- script plugins
- dynamic plugin dependencies
- Responding to changes to “dynamic” dependencies (i.e. in the
dependencies {}
sense) - Responding to dynamic inputs to build logic (e.g. properties file read by adhoc user code that externalises build logic)
- A quiet period should be respected for file system changes (e.g. wait for all copy operations to complete, wait for user to complete source edits)
- Certain inputs might be known to be immutable (e.g. cached repository dependencies have a checksum in their path and will not change, system header files)
- Changes to files behind symlinks are not respected
- Potentially allowing some way to manually trigger a rebuild (e.g. inputs changes before build finished, file system changed was captured - either correctly or incorrectly)
This story generalises the current 'PlayRun' task, adding a basic web-application + deployment domain model and some lifecycle tasks associated with this domain model.
Note that this story does not address reloading the application when source files change. This is addressed by a later story.
model {
components {
play(PlayApplicationSpec) {
deployments {
dev(PlayDevApplicationDeployment) {
httpPort
forkOptions
}
}
}
}
}
Base deployment plugin:
-
Defines the concept of a 'deployable component'
-
Defines the concept of a 'deployment spec' that can be owned by a deployable component
-
Defines the concept of a 'deployable binary' that that represents a binary that can be deployed
-
Defines
run
lifecycle task for a deployment.interface DeployableComponentSpec extends ComponentSpec { void deployments(Action<? super PolymorphicDomainObjectContainer) }
interface DeployableBinary extends BinarySpec
interface DeploymentSpec extends Named
interface Executable { void start() void stop() }
interface DeploymentExecutable extends Executable { void deploy(DeployableBinary binary) }
interface DeploymentResolver { T resolve(DeploymentSpec requirement) Class getType() }
interface DeploymentResolvers { void register(DeploymentResolver<? extends DeploymentExecutable> resolver) T resolve(Class type, DeploymentSpec requirement) }
Web application plugin:
-
Defines the concept of a 'web deployment spec': an application hosted by a web server.
interface WebDeploymentSpec { String getHost() String getPort() String getProtocol() }
Play plugin:
-
Defines a Play deployment spec
-
Adds a 'dev' PlayDeploymentSpec automatically for each play component.
-
registers a deployment resolver for a play application runner
-
Defines a ${binary}${deployment}Run task for each deployment/binary. Each task will get the deployment spec and the associated binary.
-
When a run task is executed, it will resolve the deployment executable via DeploymentResolvers to get a PlayApplicationRunner. Then it will deploy the binary and start the executable.
-
Configures
run
to depend on ${binary}${deployment}Run and ${binary}${deployment}Run to depend on the play application binary.interface PlayDeploymentSpec extends WebDeploymentSpec { PlayPlatform platform }
interface PlayApplicationSpec extends PlatformAwareComponentSpec, DeployableComponentSpec
interface PlayApplicationBinarySpec extends DeployableBinary
interface PlayApplicationRunner extends DeploymentExecutable
class PlayDeploymentResolver implements DeploymentResolver
(The following are included for comparison) War plugin:
interface WarDeploymentSpec extends WebDeploymentSpec {
String jeeVersion
}
interface WarComponentSpec extends DeployableComponentSpec
interface WarBinarySpec extends DeployableBinary {
File getWarFile()
}
interface JeeWarContainer extends DeploymentExecutable
Jetty plugin:
-
Defines the concept of a JettyDeploymentRequirement that specifies the Jee and Jetty versions
-
Defines the concept of a WarApplicationDeployment that specifies a war file to be deployed.
-
registers a deployment resolver for a JettyContainer
interface JettyDeploymentSpec extends WarDeploymentSpec { String jettyVersion }
class JettyContainer implements JeeWarContainer
class JettyDeploymentResolver implements DeploymentResolver
Component Model Report might look like this:
project (aka 'model')
+-- components
| +-- <component-name>
| +-- deployments
| +-- <deployment-name>
| +-- configuration (port, etc)
| +-- tasks
| +-- start
| +-- run
| +-- stop
- When play plugin is applied, a deployment is created for each play component (named 'dev').
- When play plugin is applied, a Run task is created for each deployment
- 'run' lifecycle task depends on each Run task
- Run task depends on Play application output
- Component report shows deployments for components and tasks for start/stop/run
- Jetty plugin should be made to take advantage of this to make the integration tests faster
- User can configure deployments for each component
- Modelling for deployment hosts -- this story focuses on getting some concepts in place for recreating the existing PlayRun task.
- Keeping track of 'long-lived processes' between Gradle builds in 'continuous mode' and at end of builds.
- Add the running deployment to the container of 'running deployments' for the build
At the end of the build, Gradle will check to see if there are any running deployments.
If so, it will wait for Ctrl+C before stopping each deployment and exiting.
This will replace the current PlayRun
implementation of Gradle with general-purpose infrastructure.
When run in continuous mode, all running deployments should be stopped before re-executing the build. This is a half-way measure until we can have reloadable applications.
- Print useful messages to the user and that tells them what's running
- PID, Component Name/Deployment Name, type-specific description
- Tell the user how to stop
- Tooling API coverage for clean-up of deployments
- Tooling API coverage for a running deployment (what does this look like, a hung build?)
Integrate deployments with continuous mode, so that if a build completes with a running deployment then that deployment is not stopped and restarted when the build re-executes due to input file change.
This should only apply for deployments that indicate that they are 'reloadable'. For now, the Play application deployment implementation will not be reloadable.
- New reloadable Jetty plugin that will reload changed content when run in continuous mode
- For play, this isn’t super important. The host and deployment are bolted pretty tightly together. What we do have is at this point is:
- A web application deployment is a running application usable at an endpoint.
- For a play application deployment (which is-a web application) the play server is-an executable thing that provides the deployment. See the ‘managed component model’ spec for other kinds of executable things. The play container can be started/stopped/restarted and this implicitly starts/stops/restarts the deployment. This is just one of several patterns, and isn’t true of all deployments or all hosts.
- For jee + jetty:
- Jetty is an executable thing that can host jee web apps. For each such hosted web app, Jetty provides a web app deployment.
- A jee web app has a bunch of deployments defined, that define where (the endpoint) but not how (use Jetty).
- A resolution process takes the deployment definitions and wires up Jetty appropriately.
Using a BuildLink implementation, allow the Play application deployment to be 'reloadable', and to automatically reload the content on browser refresh.
At this stage, the build will be re-executed whenever an input file changes, not only when requested by the BuildLink API.
Look at existing PlayRun. Should re-use WorkerProcess infrastructure.
- Receive events when the build is re-executed, including build failures
The mechanism will depend on the Play build-link library,
to inform Gradle when the application needs to be reloaded.
See Play's BuildLink.java
for good documentation about interfacing between Play and the build system.
Gradle will implement the BuildLink
interface and provide it to the application hosting NettyServer.
When a new request comes in, the Play application will call BuildLink.reload
and Gradle return a new ClassLoader containing the rebuilt application to Play.
If the application is up-to-date, BuildLink.reload
can return false
.
See the SBT implementation of this logic, which may be a helpful guide:
Play 2.2.x implementation: See PlayRun and PlayReloader
Play master branch implementation: See PlayRun, PlayReload and Reloader
Instead of rebuilding the Play application on every source file change, the application should be rebuilt only if and input file has changed AND the BuildLink API requests a reload.
??? Not sure if this will be required.
Adapt a generic build failure exception to a PlayException
that renders the exception message.
Adapt compilation failures so that the failure and content of the failing file is displayed in the Play application.
Failures in CoffeeScript compilation are rendered with content of the failing file. This mechanism will be generally applicable to custom asset compilation tasks.
When running a Play application, start the application without building any resources. Build these resources only when requested by the client.
- On each request, check whether the task which produces the requested resource has been executed or not. If not, run the task synchronously and block until completed.
- Include the transitive input of these tasks as inputs to the watch mechanism, so that further changes in these source files will trigger a restart of the application at the appropriate time.
- Failures need to be forwarded to the application for display.
Reuse the compiler daemon across builds to keep the Scala compiler warmed up. This is also useful for the other compilers.
- Maintain a registry of compiler daemons in ~/.gradle
- Daemons expire some time after build, with much shorter expiry than the build daemon.
- Reuse infrastructure from build daemon.
This allows the developer to easily add repositories to the build script using a convenience extension on the RepositoryContainer object.
buildscript {
repositories {
gradlePlay()
}
}
This should point to a virtual repository (play-public) at gradle.repo.org that's backed by the default repositories required for play functionality. Currently the following repositories would be required:
- https://repo.typesafe.com/typesafe/maven-releases (play support)
- https://repo.gradle.org/gradle/javascript-public (coffeescript and other javascript artifacts)
- Can build a basic play application using the convenience extension to specify repositories
- Can build a coffeescript-enabled play application using the convenience extension to specify repositories
Feature: Build author provides plugin for asset processing in Play application (Javascript, LESS, CoffeeScript)
Extend the standard build lifecycle to compile the front end assets to CSS and Javascript.
- Provide some built-in implementations of asset plugins
- Coffeescript -> Javascript
- LESSCSS -> CSS
- Javascript > Javascript via Google Closure
- Javascript minification, requirejs optimization
- For built-in asset plugins
- Build on public API
- Include the compiled assets in the Jar
- Define source sets for each type of source file
- Compilation should be incremental and remove stale outputs
- Expose some compiler options
JavaScript language plugin:
- Defines JavaScript library component and associated JavaScript bundle binary.
- Defines JavaScript source set type (a JavaScript bundle and JavaScript source set should be usable in either role).
- Defines transformation from JavaScript source set to JavaScript bundle.
CSS language plugin:
- Defines CSS library component and associated CSS bundle binary.
- Defines CSS source set type (a CSS bundle and CSS source set should be usable in either role).
- Defines transformation from CSS source set to CSS bundle.
CoffeeScript plugin:
- Defines CoffeeScript source set type and transformation to JavaScript bundle.
LESSCSS plugin:
- Defines LESSCSS source set type and transformation to CSS bundle.
Google Closure plugin:
- Defines transformation from JavaScript source set to JavaScript bundle.
Play plugin:
- Defines JavaScript and CSS components for the Play application.
- Wires in the appropriate outputs to assemble the Jar.
- Integration with existing Gradle javascript plugins.
- Migrating an SBT based Play project to Gradle
- Writing Gradle plugins that extend the base Play plugin
Introduce a test integration which allows Specs 2 specifications to be executed directly by Gradle, without requiring the use of the Specs 2 JUnit integration.
- Add a Specs 2 plugin
- Add some Specs 2 options to test tasks
- Detect specs 2 specifications and schedule for execution
- Execute specs 2 specifications using its API and adapt execution events
Note: no changes to the HTML or XML test reports will be made.
Allow the Scala interactive console to be launched from the command-line.
- Build the project's main classes and make them visible via the console
- Add support for client-side execution of actions
- Model the Scala console as a client-side action
- Remove console decoration prior to starting the Scala console
- Scalastyle
- SCCT
- Compile Dust templates to javascript and include in the web application image
Extend the build init plugin so that it can bootstrap a new Play project, producing the same output as activator new
except with a Gradle build instead of
an SBT build.
Allow a Play application distribution to be published to a binary repository.
Some candidates for later work:
- Improve the HTML test report to render a tree of test executions, for better reporting of Specs 2 execution (and other test frameworks)
- Support the new Java and Scala language plugins
- Improve watch mode so that only those tasks affected be a given change are executed
- Internal mechanism for plugin to inject renderer(s) into components report.
- Model language transformations, and change Play support to allow a Play application to take any JVM language as input.
- Declare dependencies on other Java/Scala libraries
- Control joint compilation of sources based on source set dependencies.
- Build multiple variants of a Play application.
- Generate an application install, eg with launcher scripts and so on.