Skip to content

DamianReeves/scala-macros-usage-with-gradle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

scala-macros-usage-with-gradle

An example of using Scala macros with gradle

I also have a related blogpost on this topic.

Using Scala Compiler Plugins From Gradle

The Scala compiler like just about every other modern programming language compiler has a plugin/extensibility story. It universally accepted that sbt is the default build tool for Scala. So users of sbt benefit from sbt’s first class support of Scala compiler plugins.

For those of us who choose (or are forced to use) other build tools, life is a little more difficult. It is more likely than not, that the ReadMe of your favorite compiler plugin does not include instructions on how to use the plugin outside of sbt. Alas, all hope is not gone. Repeat after me, “Yes we can!” You too can join in with the cool kids, and use compiler plugins.

NewType

The scalamacro.paradise plugin is a fairly popular plugin that many other plugins leverage. In this post, we are going to use macro paradise to enable scala-newtype macro expansion. The scala-newtype package includes a @newtype annotation that leverages scalamacro.paradise to allow you to generate new types that map to a wrapped type with zero runtime overhead.

import io.estatico.newtype.macros.newtype

package object types {
  @newtype case class OrderId(value: String)
}

This is similar to the newtype keyword in Haskell, from which the library derives its name.

newtype OrderId = OrderId String
The scala-newtype package/library is also very similar to theopaque type alias feature which is coming in Scala 3.0.
//Scala 3.0 / Dotty opaque type
opaque type OrderId = String

Gradle Paradise

I’m going to outline how you would go about enabling macro-paradise so you can use newtype in all its glory.

First, lets add a custom configuration to our gradle file.

We can achieve this by simply adding the following:

configurations {    
   scalaCompilerPlugin
}

With that configuration in place, we can now add the macro paradise compiler plugin.

dependencies {
  ...
  compile 'org.scala-lang:scala-library:2.12.6'    
  compile 'io.estatico:newtype_2.12:0.4.2'    
  scalaCompilerPlugin 'org.scalamacros:paradise_2.12.6:2.1.1'
  ...
}

At this point you should have something that looks like below:

configurations {
    scalaCompilerPlugin
}

dependencies {
    // Use Scala 2.11 in our library project
    compile 'org.scala-lang:scala-library:2.12.6'
    compile 'io.estatico:newtype_2.12:0.4.2'
    scalaCompilerPlugin 'org.scalamacros:paradise_2.12.6:2.1.1'


    // Use Scalatest for testing our library
    testCompile 'junit:junit:4.12'
    testCompile 'org.scalatest:scalatest_2.12:3.0.5'

    // Need scala-xml at test runtime
    testRuntime 'org.scala-lang.modules:scala-xml_2.12:1.1.0'
}

Adding Plugin to scalaCompileOptions

Okay, we now have the plugin available, but that’s not enough. Now we need to make the Scala compiler aware of the scalamacro.paradise plugin by adding it to the scalaCompileOptions of the ScalaCompile task.

tasks.withType(ScalaCompile){
    // Map plugin jars to -Xplugin parameter
    List<String> parameters =
    configurations.scalaCompilerPlugin.files.collect {
        '-Xplugin:'+ it.absolutePath
    }

    // Add existing parameters
    List<String> existingParameters = scalaCompileOptions.additionalParameters
    if (existingParameters) {
        parameters.addAll(existingParameters)
    }

    // Add whatever flags you typically add
    parameters += [
            '-language:implicitConversions',
            '-language:higherKinds'
    ]
    
    // Finally set the additionalParameters
    scalaCompileOptions.additionalParameters = parameters
}

With this in place, you should now be able to use scala-newtype in anger with successful macro generation.

Wrapping Up

Once done you should have a build.gradle file that looks something like below:

plugins {
    id 'scala'
}

configurations {
    scalaCompilerPlugin
}

dependencies {
    // Use Scala 2.11 in our library project
    compile 'org.scala-lang:scala-library:2.12.6'
    compile 'io.estatico:newtype_2.12:0.4.2'
    scalaCompilerPlugin 'org.scalamacros:paradise_2.12.6:2.1.1'


    // Use Scalatest for testing our library
    testCompile 'junit:junit:4.12'
    testCompile 'org.scalatest:scalatest_2.12:3.0.5'

    // Need scala-xml at test runtime
    testRuntime 'org.scala-lang.modules:scala-xml_2.12:1.1.0'
}


repositories {
    jcenter()
}

tasks.withType(ScalaCompile){
    // Map plugin jars to -Xplugin parameter
    List<String> parameters =
    configurations.scalaCompilerPlugin.files.collect {
        '-Xplugin:'+ it.absolutePath
    }

    // Add existing parameters
    List<String> existingParameters = scalaCompileOptions.additionalParameters
    if (existingParameters) {
        parameters.addAll(existingParameters)
    }

    // Add whatever flags you typically add
    parameters += [
            '-language:implicitConversions',
            '-language:higherKinds'
    ]
    
    // Finally set the additionalParameters
    scalaCompileOptions.additionalParameters = parameters
}

Full code example

Though this post outlined adding the scalamacro.paradise plugin, you can use this same procedure to include other Scala compiler plugins. Enjoy!