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.
The custom plugin will add the following functionality when applied to a project:
scalaVersion
property object, which will have two overridable default properties
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)createDirs
task to create the necessary directory tree, exactly as in the previous examplebuild.gradle
apply plugin: 'scala'
apply plugin: 'maven'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile gradleApi()
compile "org.scala-lang:scala-library:2.12.0"
}
Notes:
install
task used for saving the project jar to the Maven Local Repositorycompile gradleApi()
adds the gradle-api-<gradle_version>.jar
to the classpathpackage 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:
Plugin
trait of type Project
and override the apply
methodProject
instance that the plugin is applied to and you can use it for adding build logic to itscalaVersion
object propertyFirstly, 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:
scalaVersion
property defined in the project, we add one with the default valuesScalaVersion
, to use it furthervar 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"
}
scala-lang
library to the project dependencies, using the scalaVersion
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"
withScalaVersion
functionval withScalaVersion = (lib: String) => {
val libComp = lib.split(":")
libComp.update(1, s"${libComp(1)}_${scalaVersion.major}")
libComp.mkString(":")
}
project.getExtensions.getExtraProperties.set("withScalaVersion", withScalaVersion)
createDirs
task and add it to the projectDefaultTask
: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
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
.
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"
gradle createDirs
- you should now have all the source directories generatedext {
scalaVersion.major = "2.11"
scalaVersion.minor = "8"
}
println(project.ext.scalaVersion.major)
println(project.ext.scalaVersion.minor)
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.