Working With Gradle
Basic Setup
-
Create a file named
SCALA_PROJECT/build.gradle
with these contents:group 'scala_gradle' version '1.0-SNAPSHOT' apply plugin: 'scala' repositories { jcenter() mavenCentral() maven { url "https://repo.typesafe.com/typesafe/maven-releases" } } dependencies { compile group: 'org.scala-lang', name: 'scala-library', version: '2.10.6' } task "create-dirs" << { sourceSets*.scala.srcDirs*.each { it.mkdirs() } sourceSets*.resources.srcDirs*.each { it.mkdirs() } }
-
Run
gradle tasks
to see available tasks. -
Run
gradle create-dirs
to create asrc/scala, src/resources
directory. -
Run
gradle build
to build the project and download dependencies.
Create your own Gradle Scala plugin
After going through the Basic Setup example, you may find yourself repeating most part of it in every single Scala Gradle project. Smells like boilerplate code…
What if, instead of applying the Scala plugin offered by Gradle, you could apply your own Scala plugin, which would be responsible for handling all your common build logic, extending, at the same time, the already existing plugin.
This example is going to transform the previous build logic into a reusable Gradle plugin.
Luckyly, in Gradle, you can easily write custom plugins with the help of the Gradle API, as outlined in the documentation. As language of implementation, you can use Scala itself or even Java. However, most of the examples you can find throughout the docs are written in Groovy. If you need more code samples or you want to understand what lies behind the Scala plugin, for instance, you can check the gradle github repo.
Writing the plugin
Requirements
The custom plugin will add the following functionality when applied to a project:
- a
scalaVersion
property object, which will have two overridable default properties- major = “2.12”
- minor = “0”
- a
withScalaVersion
function, which applied to a dependency name, will add the scala major version to ensure binary compatibility
(sbt %%
operator might ring a bell, otherwise go here before proceeding)
- a
createDirs
task to create the necessary directory tree, exactly as in the previous example
Implementation guideline
- create a new gradle project and add the following to
build.gradle
apply plugin: 'scala'
apply plugin: 'maven'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile gradleApi()
compile "org.scala-lang:scala-library:2.12.0"
}
Notes:
- the plugin implementation is written in Scala, thus we need the Gradle Scala Plugin
- in order to use the plugin from other projects, the Gradle Maven Plugin is used; this adds the
install
task used for saving the project jar to the Maven Local Repository compile gradleApi()
adds thegradle-api-<gradle_version>.jar
to the classpath
- create a new Scala class for the plugin implementation
package com.btesila.gradle.plugins
import org.gradle.api.{Plugin, Project}
class ScalaCustomPlugin extends Plugin[Project] {
override def apply(project: Project): Unit = {
project.getPlugins.apply("scala")
}
}
Notes:
- in order to implement a Plugin, just extend
Plugin
trait of typeProject
and override theapply
method - within the apply method, you have access to the
Project
instance that the plugin is applied to and you can use it for adding build logic to it - this plugin does nothing but apply the already existing Gradle Scala Plugin
- add the
scalaVersion
object property
Firstly, we create a ScalaVersion
class, which will hold the two version properties
class ScalaVersion {
var major: String = "2.12"
var minor: String = "0"
}
One cool thing about Gradle plugins is the fact that you can always add or override specific properties. A plugin receives this kind of user input via the ExtensionContainer
attached to a gradle Project
instance. For more details, check this out.
By adding the following to the apply
method, we are basically doing this:
- if there is not a
scalaVersion
property defined in the project, we add one with the default values - otherwise, we get the existing one as instance of
ScalaVersion
, to use it further
var scalaVersion = new ScalaVersion
if (!project.getExtensions.getExtraProperties.has("scalaVersion"))
project.getExtensions.getExtraProperties.set("scalaVersion", scalaVersion)
else
scalaVersion = project.getExtensions.getExtraProperties.get("scalaVersion").asInstanceOf[ScalaVersion]
This is equivalent to writing the following to the build file of the project that applies the plugin:
ext {
scalaVersion.major = "2.12"
scalaVersion.minor = "0"
}
- add the
scala-lang
library to the project dependencies, using thescalaVersion
project.getDependencies.add("compile", s"org.scala-lang:scala-library:${scalaVersion.major}.${scalaVersion.minor}")
This is equivalent to writing the following to the build file of the project that applies the plugin:
compile "org.scala-lang:scala-library:2.12.0"
- add the
withScalaVersion
function
val withScalaVersion = (lib: String) => {
val libComp = lib.split(":")
libComp.update(1, s"${libComp(1)}_${scalaVersion.major}")
libComp.mkString(":")
}
project.getExtensions.getExtraProperties.set("withScalaVersion", withScalaVersion)
- finally, create the
createDirs
task and add it to the project
Implement a Gradle task by extending DefaultTask
:
class CreateDirs extends DefaultTask {
@TaskAction
def createDirs(): Unit = {
val sourceSetContainer = this.getProject.getConvention.getPlugin(classOf[JavaPluginConvention]).getSourceSets
sourceSetContainer forEach { sourceSet =>
sourceSet.getAllSource.getSrcDirs.forEach(file => if (!file.getName.contains("java")) file.mkdirs())
}
}
}
Note: the SourceSetContainer
has information about all source directories present in the project. What the Gradle Scala Plugin does, is to add the extra source sets to the Java ones, as you can see in theplugin docs.
Add the createDir
task to the project by appending this to the apply
method:
project.getTasks.create("createDirs", classOf[CreateDirs])
In the end, your ScalaCustomPlugin
class should look like this:
class ScalaCustomPlugin extends Plugin[Project] {
override def apply(project: Project): Unit = {
project.getPlugins.apply("scala")
var scalaVersion = new ScalaVersion
if (!project.getExtensions.getExtraProperties.has("scalaVersion"))
project.getExtensions.getExtraProperties.set("scalaVersion", scalaVersion)
else
scalaVersion = project.getExtensions.getExtraProperties.get("scalaVersion").asInstanceOf[ScalaVersion]
project.getDependencies.add("compile", s"org.scala-lang:scala-library:${scalaVersion.major}.${scalaVersion.minor}")
val withScalaVersion = (lib: String) => {
val libComp = lib.split(":")
libComp.update(1, s"${libComp(1)}_${scalaVersion.major}")
libComp.mkString(":")
}
project.getExtensions.getExtraProperties.set("withScalaVersion", withScalaVersion)
project.getTasks.create("createDirs", classOf[CreateDirs])
}
}
Installing the plugin project to the local Maven repository
This is done really easy by running gradle install
You can check the installation by going to local repository directory, usually found at ~/.m2/repository
How does Gradle find our new plugin?
Each Gradle plugin has an id
which is used in the apply
statement. For instance, by writing the following to the build file,
it translates to a trigger to Gradle to find and apply the plugin with id scala
.
apply plugin: 'scala'
In the same way, we would like to apply our new plugin in the following way,
apply plugin: "com.btesila.scala.plugin"
meaning that our plugin will have the com.btesila.scala.plugin
id.
In order to set this id, add the following file:
src/main/resources/META-INF/gradle-plugin/com.btesil.scala.plugin.properties
implementation-class=com.btesila.gradle.plugins.ScalaCustomPlugin
Afterwards, run again gradle install
.
Using the plugin
- create a new empty Gradle project and add the following to the build file
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
//modify this path to match the installed plugin project in your local repository
classpath 'com.btesila:working-with-gradle:1.0-SNAPSHOT'
}
}
repositories {
mavenLocal()
mavenCentral()
}
apply plugin: "com.btesila.scala.plugin"
- run
gradle createDirs
- you should now have all the source directories generated - override the scala version by adding this to the build file:
ext {
scalaVersion.major = "2.11"
scalaVersion.minor = "8"
}
println(project.ext.scalaVersion.major)
println(project.ext.scalaVersion.minor)
- add a dependency library that is binary compatible with the Scala version
dependencies {
compile withScalaVersion("com.typesafe.scala-logging:scala-logging:3.5.0")
}
That’s it! You can now use this plugin across all your projects without repeating the same old boilerplate.