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