https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
s

sdeleuze

06/24/2019, 8:07 AM
Hey, thanks to @snrostov my https://github.com/sdeleuze/spring-kotlin-fullstack is now using the new MP Gradle plugin but I have to say I am puzzled by the dependency structure that seems rather not conventional and harder to understand that previous one. For the JS module, we have currently:
Copy code
kotlin {
    target {
        browser()

        sourceSets {
            main {
                dependencies {
                    implementation(kotlin("stdlib-js"))
                    implementation(project(":shared"))
                }
            }
        }
    }
}
Why not:
Copy code
kotlin {
    target {
        browser()
    }
}
dependencies {
        implementation(kotlin("stdlib-js"))
        implementation(project(":shared"))
    }
For the common module, we have this pretty confusing build:
Copy code
kotlin {
    jvm()
    js {
        browser()
        nodejs()
    }

    sourceSets {
        commonMain {
            dependencies {
                implementation(kotlin("stdlib-common"))
            }
        }
        commonTest {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        getByName("jvmMain").apply {
            dependencies {
                implementation(kotlin("stdlib-jdk8"))
            }
        }
        getByName("jvmTest").apply {
            dependencies {
                implementation(kotlin("test"))
                implementation(kotlin("test-junit"))
            }
        }
        getByName("jsMain").apply {
            dependencies {
                implementation(kotlin("stdlib-js"))
            }
        }
        getByName("jsTest").apply {
            dependencies {
                implementation(kotlin("test-js"))
            }
        }
    }
}
Is it the expected final format we are going to use? cc @gaetan
s

snrostov

06/24/2019, 8:09 AM
For the JS module
Both ways supported, but there is no DSL for NPM dependencies in root
dependency
section. And it is not clear how to implement it, especially for build.gradle.kts.
r

ribesg

06/24/2019, 8:11 AM
I don’t see anything confusing here? Gradle build files are also pretty hard to understand for those not used to them, it’s much better to keep the same format everywhere IMHO
s

snrostov

06/24/2019, 8:11 AM
For the common module, we have this pretty confusing build
Could you please explain what exactly is confusing?
s

sdeleuze

06/24/2019, 8:20 AM
For the JS module, would it be possible to allow writing:
Copy code
kotlin {
    target {
        browser()
    }
}
dependencies {
    implementation(kotlin("stdlib-js"))
    implementation(project(":shared"))
    implementation(npm("html-minifier:4.0.0"))
}
r

ribesg

06/24/2019, 8:22 AM
What should that do? Add all root dependencies to all platforms?
This way of writing it looks more confusing to me than the other
s

snrostov

06/24/2019, 8:23 AM
@sdeleuze Yes, it is in our plan. I created issue to track: https://youtrack.jetbrains.com/issue/KT-32179
👍 2
r

ribesg

06/24/2019, 8:24 AM
So that would not build if you define 2+ targets?
s

snrostov

06/24/2019, 8:24 AM
@ribesg I guess the question is about single platform projects, i.e when
org.jetbraibs.kotlin.js
plugin applied.
s

sdeleuze

06/24/2019, 8:24 AM
Yes, see the sample project that is used as a basis for that discussion
r

ribesg

06/24/2019, 8:25 AM
Ok. But I could define multiple js targets, right? (sorry I don’t do js). Would that add the dependencies to all targets?
s

snrostov

06/24/2019, 8:26 AM
No, you can’t create multiple targets with
org.jetbraibs.kotlin.js
plugin. You can do it with
org.jetbraibs.kotlin.multiplatform
only.
👍 1
s

sdeleuze

06/24/2019, 8:27 AM
For the shared module I need to have a deeper look before proposing something
For the shared one, what about:
Copy code
kotlin {
	common {
		dependencies {
			implementation(kotlin("stdlib-common"))
			testImplementation(kotlin("test-common"))
			testImplementation(kotlin("test-annotations-common"))
		}
	}
    jvm {
		dependencies {
			implementation(kotlin("stdlib-jdk8"))
			testImplementation(kotlin("test"))
			testImplementation(kotlin("test-junit"))
		}
	}
    js {
        browser()
        nodejs()
		dependencies {
			implementation(kotlin("stdlib-js"))
			testImplementation(kotlin("test-js"))
		}
    }
}
IMO in term of design of the DSL it is important to leverage as much as we can existing syntax to not confusing totally users.
I understand the technical challenge
But Gradle + multiplatform is so powerful and flexible, users need a clear structure, statcially typed, reusing existing idioms.
Otherwise that will be a nightmare
r

ribesg

06/24/2019, 8:43 AM
So that would be adding another level of dependencies, right? Root dependencies, sourceSet dependencies, and now target dependencies
I mean, it does make sense
s

sdeleuze

06/24/2019, 8:44 AM
Maybe my example is not right, I know Gradle but I am still new to the new MP infra
r

ribesg

06/24/2019, 8:44 AM
But you understand that it’s different than defining dependencies for a sourceSet right?
s

sdeleuze

06/24/2019, 8:45 AM
Could you elaborate?
r

ribesg

06/24/2019, 8:46 AM
Well here you’re adding dependencies directly to a target, but targets and sourceSets are not the same thing. You can have a sourceSet without target for example
s

sdeleuze

06/24/2019, 8:47 AM
Ok but can't we have conventions to make things easier?
Each techno have its default source set rigth ?
r

ribesg

06/24/2019, 8:47 AM
What I mean is that it cannot replace what we have now, but it would sure make things more readable in most cases
Yes. Right now each target defines 2 sourceSets, main and test. But you could have a plugin adding a target defining dozens of sourceSets for whatever reason
s

sdeleuze

06/24/2019, 8:50 AM
I don't mind keeping existing syntax for advanced use case, I care about providing easy and discoverable defaults when we follow convention. The flexibility and powerfulness of Gradle needs to be balanced to be usable easily.
Currrent plugin seems to be unware of these conventions
g

gildor

06/24/2019, 8:50 AM
Why not just expose
implementationNpm
configuration?
👍 1
r

ribesg

06/24/2019, 8:50 AM
There are also some cases not yet covered by the multiplatform plugin, for example on iOS I would like to have 3 targets with the same main sourceSet, and only one of those targets would have a test sourceSet
g

gildor

06/24/2019, 8:50 AM
It will also work for Kotlin DSL
s

sdeleuze

06/24/2019, 8:52 AM
That's maybe another way to explore
g

gildor

06/24/2019, 8:53 AM
Honestly I never liked defining dependencies in sourceSets and prefer use standard dependencies block. And it already working with MPP plugin: Instead of
kotlin.sourceSets.commonMain.dependencies.api("something")
use:
dependencies.commonMainApi(project("something"))
s

sdeleuze

06/24/2019, 8:54 AM
I think I would indeed favor this option as well
So that's already usable with 1.3.40 with that syntax @gildor?
g

gildor

06/24/2019, 8:57 AM
It works with 1.3.30
and should also work with 1.3.40
s

sdeleuze

06/24/2019, 8:57 AM
Oh, I am going to try it on my app then
Thanks for the hint!
g

gildor

06/24/2019, 8:58 AM
but not sure that npm dependencies are exposed using Gradle configuration, because it uses some custom npm builder function, but I’m pretty sure it’s possible to expose
s

sdeleuze

06/24/2019, 8:59 AM
So in https://youtrack.jetbrains.com/issue/KT-32179
implementation(npm("html-minifier:4.0.0"))
should be replaced by
npmImplementation("html-minifier:4.0.0")
?
g

gildor

06/24/2019, 9:01 AM
Yes, something like that
s

sdeleuze

06/24/2019, 9:01 AM
Ok I will drop a comment
g

gildor

06/24/2019, 9:01 AM
at least it’s standard way to define custom dependencies in Gradle
s

sdeleuze

06/24/2019, 9:01 AM
Works for me, and we avoid double parenthesis so +1 for readability
g

gildor

06/24/2019, 9:01 AM
in your example
kotlin
function is just emit standard dependency id, so not really the same as npm
s

sdeleuze

06/24/2019, 9:01 AM
Yeah
I agree
g

gildor

06/24/2019, 9:01 AM
and
project
is different kind of dependency, that may be harder to implement
Also, plugins like gogradle use custom Convention for dependencies and provide own syntax, it’s also an option (which also be supported by Kotlin DSL) https://github.com/gogradle/gogradle/blob/master/docs/dependency-management.md
Python plugin tho uses exactly the same approach as suggested above: https://github.com/linkedin/pygradle/blob/master/examples/example-project/build.gradle#L22
just use custom Gradle configuration which is more limited in terms of syntax, it’s always string, or some group, id, version, but more standard and widely used
👍 1
replaced by
npmImplementation("html-minifier:4.0.0")
Or, alternatevely:
implementation("npm:html-minifier:4.0.0")
because npm doesn’t have any
group
, only name of dependency and version, so in theory it’s possible to support this syntax for all dependencies, but problem that
npm
is also valid maven group id
s

sdeleuze

06/24/2019, 9:09 AM
Yeah maybe too hackish for my taste 😉
g

gildor

06/24/2019, 9:09 AM
Yeah, probably
s

sdeleuze

06/24/2019, 9:09 AM
But the issue is open with a comment with your proposal so I guess JB will choose the best option
👍 1
g

gildor

06/24/2019, 9:13 AM
Thanks for the hint!
@sdeleuze Just to make it clear, in my example I use Kotlin DSL and have static accessors for all configurations, but only because I extract config to buildSrc plugin, so it allows Gradle to generate static accessors for me, if you just declare MPP config in the same file, you have to use dynamic syntax for configurations (except of default ones, like commonMainApi etc, because they are registered by defult by MPP plugin):
jvmMainApi("something")
becomes
"jvmMainApi"("something")
without static accessors, but IMO it’s still better, and if you want to declare some common configuration, just use buildSrc, so it will make all the configs type safe
👍 1
But again, it’s not something custom, it works out of the box like that for all configurations in Kotlin DSL: static accessors are generating for them if they registered on configuration time
s

sdeleuze

06/24/2019, 9:16 AM
In your project, how do you extract config to buildSrc plugin exactly?
g

gildor

06/24/2019, 9:21 AM
No, I just use custom precompiled plugin
s

sdeleuze

06/24/2019, 9:21 AM
Oh
seee
s

sdeleuze

06/24/2019, 9:22 AM
So maybe we could open an issue for that beiing available by default ?
Or at least with just a little of config
Discoverability is key and we can't expect people using
"jvmMainApi"("something")
g

gildor

06/24/2019, 9:24 AM
See, problem that usually, if you just have 1 module and do not extract config to custom plugin, you define target platforms on build script runtime, so you don’t have those configurations in configuration time, and don’t have static accessors for them
Discoverability is key and we can’t expect people using
I agree
s

sdeleuze

06/24/2019, 9:24 AM
Yeah
I see the technical challenge
g

gildor

06/24/2019, 9:24 AM
but ut’s just how this configuration approach works
s

snrostov

06/24/2019, 9:25 AM
Discoverability is key and we can’t expect people using
"jvmMainApi"("something")
Just an idea about build.gradle.kts: what do you think about
jvm.main.api("something")
?
g

gildor

06/24/2019, 9:26 AM
so what is
jvm
in this case? Gradle Convention for dependencies block?
s

snrostov

06/24/2019, 9:26 AM
reference to kotlin target created from preset
g

gildor

06/24/2019, 9:27 AM
As I understand
jvm
here is just default name of
jvm
configuration, so if you define it like:
jvm("java12") { // some MPP plugin }
Dependency should have configuration like
java12MainApi
reference to kotlin target created from preset
Shouldn’t it be
kotlin.jvm.main.api("something")
than?
I just not sure how to make it type safe in this case
s

snrostov

06/24/2019, 9:28 AM
Copy code
jvm("java12") { // some MPP plugin }
maybe we can replace it with
Copy code
val java12 by jvm {  }
g

gildor

06/24/2019, 9:28 AM
yeah, it will work for sure
a bit non-conventional, but may solve problem of dynamic platform declearation
s

sdeleuze

06/24/2019, 9:38 AM
@gildor If I use that I have a
Configuration with name 'jvmMainImplementation' not found
error, what did I miss?
Copy code
plugins {
    kotlin("multiplatform")
}

repositories {
    mavenCentral()
}

dependencies {
	commonMainImplementation(kotlin("stdlib-common"))
	commonTestImplementation(kotlin("test-common"))
	commonTestImplementation(kotlin("test-annotations-common"))

	"jvmMainImplementation"(kotlin("stdlib-jdk8"))
	"jvmTestImplementation"(kotlin("test"))
	"jvmTestImplementation"(kotlin("test-junit"))

	"jsMainImplementation"(kotlin("stdlib-js"))
	"jsTestImplementation"(kotlin("test-js"))
}

kotlin {
	jvm()
    js {
        browser()
        nodejs()
    }
}
s

snrostov

06/24/2019, 9:39 AM
@gildor About npm dependencies declaration: added comment here https://youtrack.jetbrains.com/issue/KT-32179#focus=streamItem-27-3536861.0-0. Feel free to edit/comment.
g

gildor

06/24/2019, 9:52 AM
@Alexandre Delattre Swap
dependencies
and
kotlin
blocks
Because again, it’s defined in runtime, so configuration just not available
s

sdeleuze

06/24/2019, 12:37 PM
I was hoping the version with quotes (no static accessor) to work, but it fails even on command line.
Can I fix that without buildSrc?
d

Dico

06/24/2019, 1:01 PM
You can always do this in the root dependencies blpck:
"browserMainImplementation"("dependencyNotation")
g

gaetan

06/28/2019, 7:12 AM
To be sure to have a valid DSL, we should be sure to have all uses cases and see how the DSL would apply to these cases. You didn’t talk about one of them. Suppose you have an application with admin and users modules. Each of them depends on a core module. You have now also to manage the dependencies between modules: AdminCommon -> CoreCommon AdminJS -> CoreJs -> CoreCommon. How does that fit into the new MPP plugin? The current documentation is not clear at all about that. In our data2viz library, we have a lot of these dependencies.
s

snrostov

06/28/2019, 7:24 AM
You should have 2 mpp projects: admin and core. Within these projects, there should be, for example, JVM and JS target. To declare dependency from one project to another, you should just put
project(":core")
in
commonMainApi
configuration. It is enough to specify a single
project('...')
dependency in a source set’s dependencies, and the compilations that include the source set will receive a corresponding platform-specific artifact of that project. This is main reason for new mpp project layout: now platform-specific artifact selected automatically. https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#adding-dependencies
g

gaetan

06/28/2019, 7:32 AM
Ok. However, how it works with a project that has multiple JVM targets? For example, a web server and a JavaFx one. They must be split into different projects to be used as dependencies?
s

snrostov

06/28/2019, 7:36 AM
They should provide some attribute to select proper artifact variant: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets
Copy code
kotlin {
    /* ... */
    
    // Configure the attributes of the 'jvm6' target:
    jvm("jvm6").attributes { /* ... */ }
}
Corresponding dependency artifact will be selected by matching consumer and dependency attributes. More info about this Gradle feature can be found here: https://docs.gradle.org/current/userguide/dependency_management_attribute_based_matching.html
As alternative, you can specify artifact manually, for example:
"jvm6MainApi"("com.example:foo-jvm6:1.0")
@h0tk3y please correct me if I wrong.
g

gaetan

06/28/2019, 8:07 AM
Thanks for this information. Didn’t know about it. In fact, it’s in the documentation: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#disambiguating-targets I think that one of the biggest problems now is the comprehension of how it’s working. The current “reference” documentation is mixing a few types of documentation: how-to, reference, and explanation. See https://www.divio.com/blog/documentation/ Documentation should clearly identify each part. The first chapter “project structure” can highlight what is specific to MPP and what is Gradle concept.
👍 1
2 Views