Any plans for having conditional compiling?

Hi people!

I wonder if there are any plans for introducing features for conditional compiling so we can support both Java and Javascript targets in the same project?

Maybe something like a convention based on file extensions? For example,

  • Canvas.js.kt
  • Canvas.java.kt

and then the compiler choses file accordingly to what the current backend is.

How does this sound? If not, what is/will the standard practice be for this problem?

Regards, Tommy

1 Like

Why not factor platform-specific parts into separate modules?

1 Like

Oddly enough I didn't think of that. :8}

Incidentally I did just that recently on a project (I fixed up the maven plugin so it can support different source paths for different execution phases (JVM 'compile' versus 'js' etc) such as in this pom.xml. I did cheat in the standard library in kotlin and use a naming convention (excluding *JVM.kt files on the JS compile) which lets you keep code closer together; but I guess separate directory trees is maybe cleaner.

It might be nice for IDEA to have a view where the ‘common’, ‘jvm’ and ‘js’ kotlin code could be viewed in a single source tree to avoid having to do too much tree expanding/collapsing.

1 Like

Hi, I’d like kotlin to have some conditional compile control syntax
like #if
from my android developer experience:
I hope i can write such code:

#if DEBUG
open
#endif
class MyActivity:Activity(){}

that means I want a open class in Debug Mode. while a final class in release mode.

is it a library? Why would you need it to be a final class in release mode? Which advantages does it bring?

Would love this for examples like the following

#ifnot java_8
Thread.onSpinWait()
#end

The common answer to this is reflection. I know java 8 code can run on newer versions, but some times its nice to have enhancements based off the version compiled for.

It can be usuful for conditional compilation, when setting constants.

#define TEST_URL true

#ifdef TEST_URL
    const val DOMAIN = "test.com"
    const val URL = "https://test.$DOMAIN/"
#else
    const val DOMAIN = "example.com"
    const val URL = "https://$DOMAIN/"
#endif

compatible with with java 8+

Agrona ThreadHints

JVM:

@JvmField
val option = System.getProperty("my.option")

// JIT removes unnecessary branch at first execution
if (option) {
	// ...
} else {
   // ...
}

Now pass your configurations values to the application using CLI argument -D<option_name>:

java -jar jarName -Dmy.option=true

Initialize “my.option” system property in any way you want, most importantly, do this before loading java class in which option property is defined (JVM loads classes lazily, at first access).

fun main() {
	System.setProperty("my.option", "true")

	// ...
}

Android:

build.gradle:

buildConfigField "boolean", "MY_OPTION", "true" 

In your code:

// unnecessary branch removed at compile time
if (BuildConfig.MY_OPTION) {
	// ...
} else {
	// ...
}

Yes, it is a good variant to set an option in build.gradle. It will be useful for a simple use-case when we want to set constants. In this case we can set several constants (but as variables) eighter with repeating a block, or destructing them:
1.

val const1 = if (BuildConfig.MY_OPTION) {
// ...
} else {
// ...
}
val const2 = ...
val (const1, const2, ...) = if (BuildConfig.MY_OPTION) {
...
} else {
...
}

@wdoker , Your method works, but it is not good as the #if conditional compilation. Just think about what if that optional feature needs a bulk array? That bulk array can be wiped out if the #if feature is available. For that reason, Kotlin should add the #if conditional compiltion feature.

1 Like

I would love this feature, especially for game scripts.
I’m working on godot Kotlin, and we have few check in our code that we’d like to have only in debug because real time is expensive (example: godot-kotlin-jvm/kt/godot-library/src/main/kotlin/godot/core/Vector3.kt at 10ab18864229f5178fc20e0be586d321e306be26 · utopia-rise/godot-kotlin-jvm · GitHub)

I understand that we have system properties that enable jit to get rid of blocks, but our code is cross platform. We use hotspot and graal native image for desktops, ART for android, and graal native image for iOS. So we need a “cross VM solution”. For now we don’t have better option than a compiler plugin.

PS: I think that’s one essential feature for game dev c# has that Kotlin miss

You can very easily turn that into a singular constant if-check in release builds and the fully-fledged normalised checking in debug builds.
Simply have a custom require function that knows about a const val DEBUG: Boolean like so:

inline fun requireIfDebug(condition: () -> Boolean, message: () -> String) {
  if(DEBUG) require(condition(), message)
}

IIRC, the compiler folds constants at compile time, and so the requireIfDebug function and all its usages will turn into an if(false) in release builds, which, IIRC again, makes the compiler completely take out the code (and regardless if it doesn’t or not, it’s trivial to run something like proguard first to minimise and optimise the code, and then run it through graalvm

1 Like

It seems you remember well !
I made a quick test with this

const val DEBUG = true

Then in Vector2:

    /**
     * Returns the result of spherical linear interpolation between this vector and b, by amount t.
     * t is in the range of 0.0 - 1.0, representing the amount of interpolation.
     *
     * Note: Both vectors must be normalized.
     */
    fun slerp(b: Vector2, t: RealT): Vector2 {
        if (DEBUG) {
            require(this.isNormalized() && b.isNormalized()) { "Both this and b vector must be normalized!" }
        }
        val theta: RealT = angleTo(b)
        return rotated((theta * t))
    }

Note: if (DEBUG) is line 402.

This leads to this bytecode:

   L0
    ALOAD 1
    LDC "b"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 402 L1
    NOP
   L2
    LINENUMBER 403 L2
    ALOAD 0
    INVOKEVIRTUAL godot/core/Vector2.isNormalized ()Z
    IFEQ L3
    ALOAD 1
    INVOKEVIRTUAL godot/core/Vector2.isNormalized ()Z
    IFEQ L3
   L4
    ICONST_1
    GOTO L5
   L3
    ICONST_0
   L5
    ISTORE 4
   L6
   L7
    ILOAD 4
    IFNE L8
   L9
   L10
    LINENUMBER 521 L10
   L11
    ICONST_0
    ISTORE 5
   L12
    LINENUMBER 403 L12
    LDC "Both this and b vector must be normalized!"
   L13
   L14
    LINENUMBER 403 L14
    ASTORE 5
   L15
    NEW java/lang/IllegalArgumentException
    DUP
    ALOAD 5
    INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;
    INVOKESPECIAL java/lang/IllegalArgumentException.<init> (Ljava/lang/String;)V
    ATHROW
   L8
    LINENUMBER 405 L8
    ALOAD 0
    ALOAD 1
    INVOKEVIRTUAL godot/core/Vector2.angleTo (Lgodot/core/Vector2;)D
    DSTORE 4
   L16
    LINENUMBER 406 L16
    ALOAD 0
    DLOAD 4
    DLOAD 2
    DMUL
    INVOKEVIRTUAL godot/core/Vector2.rotated (D)Lgodot/core/Vector2;
    ARETURN
   L17
    LOCALVARIABLE $i$a$-require-Vector2$slerp$1 I L12 L14 5
    LOCALVARIABLE theta D L16 L17 4
    LOCALVARIABLE this Lgodot/core/Vector2; L0 L17 0
    LOCALVARIABLE b Lgodot/core/Vector2; L0 L17 1
    LOCALVARIABLE t D L0 L17 2
    MAXSTACK = 5
    MAXLOCALS = 6

As we can see we have no operation for if line, and then all require expression code.

If I set

const val DEBUG = false

Then this leads to this bytecode:

   L0
    ALOAD 1
    LDC "b"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 402 L1
    NOP
   L2
    LINENUMBER 405 L2
    ALOAD 0
    ALOAD 1
    INVOKEVIRTUAL godot/core/Vector2.angleTo (Lgodot/core/Vector2;)D
    DSTORE 4
   L3
    LINENUMBER 406 L3
    ALOAD 0
    DLOAD 4
    DLOAD 2
    DMUL
    INVOKEVIRTUAL godot/core/Vector2.rotated (D)Lgodot/core/Vector2;
    ARETURN
   L4
    LOCALVARIABLE theta D L3 L4 4
    LOCALVARIABLE this Lgodot/core/Vector2; L0 L4 0
    LOCALVARIABLE b Lgodot/core/Vector2; L0 L4 1
    LOCALVARIABLE t D L0 L4 2
    MAXSTACK = 5
    MAXLOCALS = 6

All require code is removed.

As we’re “dexifying” our jar for android and graal native image rely on jvm compiled bytecode, this should do the job !

1 Like

I created a gradle plugin to make “conditional compilation” by using const val generated with kotlin-poet according to definitions in gradle script:

Feel free to contribute !