Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using macro-paradise and cross-compiling with 2.12/2.13

Tags:

sbt

Since Scala 2.13, macro-paradise has been inlined in the compiler and is available via a compiler flag:

Compile / scalacOptions += "-Ymacro-annotations"

For reference, in previous versions of Scala, macro-paradise was available via a compiler plugin:

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)

What is the canonical way of conditionally add the first setting or the second, according to the value of the Scala version, in a build targetting both Scala 2.12 and 2.13?

I would like to write the following but it doesn’t work:

CrossVersion.partialVersion(scalaVersion.value) match {
  case Some((2, n)) if n >= 13 => Compile / scalacOptions += "-Ymacro-annotations"
  case _ => addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
}

It fails with the following error:

error: `value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
      CrossVersion.partialVersion(scalaVersion.value) match {
                                               ^

In the meantime, I can use the following workaround but I wish a simpler solution was supported:

Compile / scalacOptions ++= {
  CrossVersion.partialVersion(scalaVersion.value) match {
    case Some((2, n)) if n >= 13 => "-Ymacro-annotations" :: Nil
    case _ => Nil
  }
}

libraryDependencies ++= {
  CrossVersion.partialVersion(scalaVersion.value) match {
    case Some((2, n)) if n >= 13 => Nil
    case _ => compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) :: Nil
  }
}
like image 941
Julien Richard-Foy Avatar asked Jan 27 '19 19:01

Julien Richard-Foy


2 Answers

If you would like to write

CrossVersion.partialVersion(scalaVersion.value) match {
  case Some((2, n)) if n >= 13 => Compile / scalacOptions += "-Ymacro-annotations"
  case _ => addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
}

one option is defining a SBT custom command like so

def compileWithMacroParadise: Command = Command.command("compileWithMacroParadise") { state =>
  import Project._
  val extractedState = extract(state)
  val stateWithMacroParadise = CrossVersion.partialVersion(extractedState.get(scalaVersion)) match {
    case Some((2, n)) if n >= 13 => extractedState.appendWithSession(Seq(Compile / scalacOptions += "-Ymacro-annotations"), state)
    case _ => extractedState.appendWithSession(addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), state)
  }
  val (stateAfterCompileWithMacroParadise, _) = extract(stateWithMacroParadise).runTask(Compile / compile, stateWithMacroParadise)
  stateAfterCompileWithMacroParadise
}

commands ++= Seq(compileWithMacroParadise),
addCommandAlias("compile", "compileWithMacroParadise")

sbt compile should now make appropriate modifications to build state (stateWithMacroParadise) before running the compile task.

like image 150
Mario Galic Avatar answered Oct 25 '22 15:10

Mario Galic


Fully working example, add this code in a Compiler.scala file in your project directory:

import sbt._
import sbt.Keys._

object Compiler extends AutoPlugin {
  override def trigger = allRequirements

  override def projectSettings: Seq[Def.Setting[_]] =
    Seq(
      libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match {
        case Some((2, x)) if x < 13 =>
          Seq(
            compilerPlugin(("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)),
            "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6"
          )
        case _ => Nil
      }),
      Compile / scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match {
        case Some((2, x)) if x >= 13 =>
          Seq("-Ymacro-annotations")
        case _ => Nil
      })
    )

}

Note that you have to use projectSettings, buildSettings will not work

The "scala-collection-compat" dependency is another one you typically want when cross compiling 2.12 and 2.13. It let's you do

import scala.jdk.CollectionConverters._

Instead of using the deprecated scala.collection.JavaConverters

like image 31
gogstad Avatar answered Oct 25 '22 15:10

gogstad