Feedback + questions about multitarget (JVM/JS/Native) projects/libraries


#1

Hello, I’m the author of some Kotlin libraries that I have been developing in the past months and that are going to reach 1.0 soon. Even when this post is pretty big, it is really important to me to be able to use all kotlin language targets:

There are some inter-dependencies between those libraries. But they are pure kotlin libraries. And they are designed to be portable and to hook with platform-specific implementations.
My current approach is to generate JVM code and transcompile to other targets using my jtransc transcompiler. Creating the code and debugging in the JVM and then creating compiling for targets that just work. But I’m eager to use direct kotlin targets, specially now that there is a JS target and a LLVM target in the works.
I need some help figuring out how to do this (and if there is documentation related to this, please I really appreciate some links). And give feedback about my current workflow / whishlist.

Reflection

The most important part for me here is reflection. Right now I’m using it for three things:

Service Loader

I’m using service loader to find implementations. I have pure kotlin core libraries. For example korag-core, that just provides abstract classes and common classes, and then I’m using a Service Loader (compatible with jtransc) to find implementations in the final injected libraries. In my case I’m using. Something like this that JTransc interprets in the file:


korag/korag/resources/META-INF/services/com.soywiz.korag.AGFactory:

com.soywiz.korag.webgl.AGFactoryWebgl # <target=js>
com.soywiz.korag.awt.AGFactoryAwt # <target=jvm>

This allows me to just use one library so I do not need to replicate each library per target.

This part could be solved by having an interface and an implementation in a single place per library and then link different libraries per traget. But I do not feel it that robust. So one whishlist would be something like a ServiceLoader compatible with all kotlin targets and resolved at compile-time. If there is a mechanism for this in kotlin already, I would like to know it.

Dependency Injection

This is one of the reasons I would like to have reflection. I need to know constructors and the parameter types of a function/constructor in order to be able to do this properly. An opt-in full reflection would be really appreciated for this. For this a meta-programming processor would fit. But that would probably totally break any kind of incremental-compilation, at least without problematic bugs. I already experienced that with D and Haxe. So I prefer runtime reflection.

One example would be this. I’m requesting to the asynchronous injector SelectLevelScene, it detects the main constructor and requests other dependencies from there, also injecting annotations and using custom factories when annotated.

class SelectLevelScene(
	@Path("kotlin.atlas") val atlas: Atlas,
	val ui: UIFactory
) : Scene() {
	suspend override fun sceneInit(sceneView: Container) {
		sceneView += ui.koruiFrame {
			horizontal {
				padding = Padding(0.2.em)
				button("EASY").click { sceneContainer.pushTo<IngameScene>(Difficulty.EASY) }
				button("MEDIUM").click { sceneContainer.pushTo<IngameScene>(Difficulty.MEDIUM) }
				button("HARD").click { sceneContainer.pushTo<IngameScene>(Difficulty.HARD) }
			}
		}
	}
}

De/Serialization

I’m using reflection for serialization and deserialization. I’m building classes from jsons/xmls/yamls. I know that you can use untyped objects like if they were real objects in JS, but they don’t have the object hierarchy so instance methods won’t work (even when you can create ), and I need mechanisms that works everywhere without changing code and that builds proper objects. So again full reflection would be awesome and won’t require to create custom compile-time code for each need I have.

Using the same source root for JS/JVM/Native code

I know of people that is creating symbolic links in order to be able to compile common data classes for JVM and JS. What I would like to have is to create a JAR with .class and code that just works on JS/Native too. But a mechanism that allows me to use the same source root with gradle/intellij for both and generate two libraries would be enough too.

Tree Shaking

This is an optimization and not required for me to start working on pure kotlin targets, but highly desirable for client-side code. With jtransc I’m doing treeshaking. That means that starting by the main method + service loaders + manually linked code I’m discovering other methods/classes/annotations + main constructors when classes are referenced and just including them in the final output. Similar like dart does. Or proguard, but without requiring rules and using annotations in code to define rules for very specific cases.


So this is my feedback/questions about this. What’s the status of those points? It is already possible to make my libraries compatible with kotlin.js? If not, there is something I can do to help? If there are already guideliness about how is Kotlin to face this, but it is a problem related to lack of time, I can help implementing optional full reflection in kotlin.js or things like that.

Thanks for your time!


#2

For this a meta-programming processor would fit. But that would probably totally break any kind of incremental-compilation, at least without problematic bugs. I already experienced that with D and Haxe. So I prefer runtime reflection.

That’s true, but personally I’m sceptical about reflection. Of course, some experiments required, but without them my personal estemiate is: it won’t be practical for applications. As for me, it’s better to solve problems with incremental compilation support in metaprogramming.

What’s the status of those points?

There are no reflection and/or metaprogramming support in roadmap for 1.2, as well as DI and serialization. We are working on dead-code elimination and work is very close to be completed, I hope we’ll merge it to master quite soon.


#3

Thanks for the info!

Then I will continue with my current approach until you decide how to do this after 1.2. Either it is reflection or metaprogramming if you need help because of timing I offer myself to help you with that once decided.
If you implement metaprogramming; in the worst case I could always implement full reflection capabilities as a metaprogramming hook and offer it as a plugable optional stuff.
Also I could try to optimize it for my injector to just reflect statically referenced classes and detect deserializers to do the same.
If you can also solve incremental compilation with metaprogramming it would be great too.