Thread
#multiplatform
    sdeleuze

    sdeleuze

    3 years ago
    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:
    kotlin {
        target {
            browser()
    
            sourceSets {
                main {
                    dependencies {
                        implementation(kotlin("stdlib-js"))
                        implementation(project(":shared"))
                    }
                }
            }
        }
    }
    Why not:
    kotlin {
        target {
            browser()
        }
    }
    dependencies {
            implementation(kotlin("stdlib-js"))
            implementation(project(":shared"))
        }
    For the common module, we have this pretty confusing build:
    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
    snrostov

    snrostov

    3 years ago
    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

    3 years ago
    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
    snrostov

    snrostov

    3 years ago
    For the common module, we have this pretty confusing build
    Could you please explain what exactly is confusing?
    sdeleuze

    sdeleuze

    3 years ago
    For the JS module, would it be possible to allow writing:
    kotlin {
        target {
            browser()
        }
    }
    dependencies {
        implementation(kotlin("stdlib-js"))
        implementation(project(":shared"))
        implementation(npm("html-minifier:4.0.0"))
    }
    r

    ribesg

    3 years ago
    What should that do? Add all root dependencies to all platforms?
    This way of writing it looks more confusing to me than the other
    snrostov

    snrostov

    3 years ago
    @sdeleuze Yes, it is in our plan. I created issue to track: https://youtrack.jetbrains.com/issue/KT-32179
    r

    ribesg

    3 years ago
    So that would not build if you define 2+ targets?
    snrostov

    snrostov

    3 years ago
    @ribesg I guess the question is about single platform projects, i.e when
    org.jetbraibs.kotlin.js
    plugin applied.
    sdeleuze

    sdeleuze

    3 years ago
    Yes, see the sample project that is used as a basis for that discussion
    r

    ribesg

    3 years ago
    Ok. But I could define multiple js targets, right? (sorry I don’t do js). Would that add the dependencies to all targets?
    snrostov

    snrostov

    3 years ago
    No, you can’t create multiple targets with
    org.jetbraibs.kotlin.js
    plugin. You can do it with
    org.jetbraibs.kotlin.multiplatform
    only.
    sdeleuze

    sdeleuze

    3 years ago
    For the shared module I need to have a deeper look before proposing something
    For the shared one, what about:
    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

    3 years ago
    So that would be adding another level of dependencies, right? Root dependencies, sourceSet dependencies, and now target dependencies
    I mean, it does make sense
    sdeleuze

    sdeleuze

    3 years ago
    Maybe my example is not right, I know Gradle but I am still new to the new MP infra
    r

    ribesg

    3 years ago
    But you understand that it’s different than defining dependencies for a sourceSet right?
    sdeleuze

    sdeleuze

    3 years ago
    Could you elaborate?
    r

    ribesg

    3 years ago
    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
    sdeleuze

    sdeleuze

    3 years ago
    Ok but can't we have conventions to make things easier?
    Each techno have its default source set rigth ?
    r

    ribesg

    3 years ago
    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
    sdeleuze

    sdeleuze

    3 years ago
    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
    gildor

    gildor

    3 years ago
    Why not just expose
    implementationNpm
    configuration?
    r

    ribesg

    3 years ago
    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
    gildor

    gildor

    3 years ago
    It will also work for Kotlin DSL
    sdeleuze

    sdeleuze

    3 years ago
    That's maybe another way to explore
    gildor

    gildor

    3 years ago
    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"))
    sdeleuze

    sdeleuze

    3 years ago
    I think I would indeed favor this option as well
    So that's already usable with 1.3.40 with that syntax @gildor?
    gildor

    gildor

    3 years ago
    It works with 1.3.30
    and should also work with 1.3.40
    sdeleuze

    sdeleuze

    3 years ago
    Oh, I am going to try it on my app then
    Thanks for the hint!
    gildor

    gildor

    3 years ago
    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
    sdeleuze

    sdeleuze

    3 years ago
    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")
    ?
    gildor

    gildor

    3 years ago
    Yes, something like that
    sdeleuze

    sdeleuze

    3 years ago
    Ok I will drop a comment
    gildor

    gildor

    3 years ago
    at least it’s standard way to define custom dependencies in Gradle
    sdeleuze

    sdeleuze

    3 years ago
    Works for me, and we avoid double parenthesis so +1 for readability
    gildor

    gildor

    3 years ago
    in your example
    kotlin
    function is just emit standard dependency id, so not really the same as npm
    sdeleuze

    sdeleuze

    3 years ago
    Yeah
    I agree
    gildor

    gildor

    3 years ago
    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
    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
    sdeleuze

    sdeleuze

    3 years ago
    Yeah maybe too hackish for my taste 😉
    gildor

    gildor

    3 years ago
    Yeah, probably
    sdeleuze

    sdeleuze

    3 years ago
    But the issue is open with a comment with your proposal so I guess JB will choose the best option
    gildor

    gildor

    3 years ago
    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
    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
    sdeleuze

    sdeleuze

    3 years ago
    In your project, how do you extract config to buildSrc plugin exactly?
    gildor

    gildor

    3 years ago
    No, I just use custom precompiled plugin
    sdeleuze

    sdeleuze

    3 years ago
    Oh
    seee
    sdeleuze

    sdeleuze

    3 years ago
    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")
    gildor

    gildor

    3 years ago
    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
    sdeleuze

    sdeleuze

    3 years ago
    Yeah
    I see the technical challenge
    gildor

    gildor

    3 years ago
    but ut’s just how this configuration approach works
    snrostov

    snrostov

    3 years ago
    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")
    ?
    gildor

    gildor

    3 years ago
    so what is
    jvm
    in this case? Gradle Convention for dependencies block?
    snrostov

    snrostov

    3 years ago
    reference to kotlin target created from preset
    gildor

    gildor

    3 years ago
    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
    snrostov

    snrostov

    3 years ago
    jvm("java12") { // some MPP plugin }
    maybe we can replace it with
    val java12 by jvm {  }
    gildor

    gildor

    3 years ago
    yeah, it will work for sure
    a bit non-conventional, but may solve problem of dynamic platform declearation
    sdeleuze

    sdeleuze

    3 years ago
    @gildor If I use that I have a
    Configuration with name 'jvmMainImplementation' not found
    error, what did I miss?
    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()
        }
    }
    snrostov

    snrostov

    3 years ago
    @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.
    gildor

    gildor

    3 years ago
    @Alexandre Delattre Swap
    dependencies
    and
    kotlin
    blocks
    Because again, it’s defined in runtime, so configuration just not available
    sdeleuze

    sdeleuze

    3 years ago
    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?
    Dico

    Dico

    3 years ago
    You can always do this in the root dependencies blpck:
    "browserMainImplementation"("dependencyNotation")
    gaetan

    gaetan

    3 years ago
    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.
    snrostov

    snrostov

    3 years ago
    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
    gaetan

    gaetan

    3 years ago
    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?
    snrostov

    snrostov

    3 years ago
    They should provide some attribute to select proper artifact variant: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets
    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.
    gaetan

    gaetan

    3 years ago
    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.