search term:

ifdef 0.3.0: conditional compilation in Scala

@ifdef is a Scala compiler plugin that impelements conditional compilation in Scala. ifdef 0.3.0 supports Scala.JS and Scala Native.

Scala Version JVM JS (1.x) Native (0.5.x)
3.x
2.13.x
2.12.x

setup

Put this in project/plugins.sbt:

addSbtPlugin("com.eed3si9n.ifdef" % "sbt-ifdef" % "0.3.0")

Source is available at https://github.com/eed3si9n/ifdef

conditional compilation

Rust defines conditional source code to be:

code that may or may not be considered a part of the source code depending on certain conditions

ifDefDeclations are names or key-value pairs that are either set or unset to control the condition. Two declarations that are configured in sbt-ifdef 0.3.0 by default are:

  1. build configuration names: compile, test
  2. scalaBinaryVersion: scalaBinaryVersion:3

but you can use your imagination to expand to other axes that may have previously required different directories to cross build.

in-source unit tests

One use of conditional compilation found in Rust is embedding unit tests into the library code as follows:

package example

import com.eed3si9n.ifdef.ifdef

class A:
  def foo: Int = 42
end A

@ifdef("test")
class ATest extends munit.FunSuite:
  test("foo"):
    val actual = new A().foo
    val expected = 42
    assertEquals(actual, expected)
end ATest

For many of the unit tests, I think this would be useful rather than splitting tests into different source code.

Scala cross building

Here’s an example of using @ifdef and @ifndef with scalaBinaryVersion:

package example

import com.eed3si9n.ifdef.{ ifdef, ifndef }
import java.util.{ Map => JMap }

class A {
  @ifdef("scalaBinaryVersion:2.12")
  def convertMapToScala[K, V](jmap: JMap[K, V]): Map[K, V] = {
    // -Xsource:3 lets us use * in Scala 2.12. nice
    import scala.collection.JavaConverters.*
    Map.empty ++ jmap.asScala
  }

  @ifndef("scalaBinaryVersion:2.12")
  def convertMapToScala[K, V](jmap: JMap[K, V]): Map[K, V] = {
    import scala.jdk.CollectionConverters.*
    Map.empty.concat(jmap.asScala)
  }
}

The above is the same example Stefan Zeiger used in the now-closed SIP-NN Language support for conditional compilation proposal. The example also demonstrates that in the above @ifdef and @ifndef are processed prior to the typer phase, otherwise the code will fail to compile on Scala 2.12 because it is unaware of scala.jdk.CollectionConverters.*.

new in 0.3.0

Reference