Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gradle 5 Kotlin DSL: Common Tasks & Maven Artifacts in multi-modules projects

I really would like to appreciate Gradle 5 especially in combination with the new Kotlin DSL, but I’m having a very hard time to get (in my eyes) a very, very simple and common build running with Gradle.

Task

Release a Java library with several interdependent submodules in Maven default directory layout as high-quality Maven artifacts/repository in a to-the-point, simple Gradle build (i.e. DRY).

Therefore: Have a root project as umbrella which defines & contains all the common configuration (practically all except the real dependencies).

My current struggles

I ported my current "results" to a sample project on Github and asked this question in the Gradle forum already.

Currently I'm failing to declare the necessary task to provide standard -sources and -javadoc artifacts in my central build.

For example these three "solutions" which you'll find on looking for a Kotlin DSL based solutions all do no (longer) work in a multi-module scenario:

  • https://stackoverflow.com/a/48070667
  • https://stackoverflow.com/a/52596969/1237653
  • and even the offical "Maven Publish" documentation is only working in a single-module scenario.

Incomplete solution (/build.gradle.kts)

Complete example see on Github: https://github.com/bentolor/gradle-maven-multimodule-kotlindsl

subprojects {
    apply(plugin = "java-library")
    apply(plugin = "maven-publish")
    group = "de.bentolor.sampleproject"
    version = "0.1.0"

    repositories {
        jcenter()
    }

    dependencies {
        // Dependencies used in EVERY module
        "compile"("commons-logging:commons-logging:1.2")
        "testImplementation"("junit:junit:4.12")
    }

    tasks {
        // not working
        /*register("sourcesJar", Jar::class.java) {
            from(sourceSets.main.get().allJava)
            classifier = "sources"
        }*/

       // not working, eiher
       /* task<Jar>("sourcesJar") {
            from(sourceSets.main.get().allJava)
            classifier = "sources"
       } */
    }

    configure<JavaPluginExtension> {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    configure<PublishingExtension> {
        publications {
            create<MavenPublication>(project.name) {
                from(components["java"])
                // won't work, beause inaccessible declaration in `tasks{}`-Block
                //add("archives", javadocJar)
                //add("archives", sourcesJar)
            }
        }

        repositories {
            mavenLocal()
        }
    }
}

Example submodule /module2/build.gradle.kts

group = "de.bentolor.sampleproject.module2"

dependencies {
    compile(project(":module1"))
}
like image 768
bentolor Avatar asked Dec 05 '18 11:12

bentolor


People also ask

What is the Kotlin Gradle DSL?

Gradle's Kotlin DSL provides an alternative syntax to the traditional Groovy DSL with an enhanced editing experience in supported IDEs, with superior content assist, refactoring, documentation, and more.

What are the default Gradle tasks?

Gradle allows you to define one or more default tasks that are executed if no other tasks are specified. defaultTasks 'clean', 'run' tasks. register('clean') { doLast { println 'Default Cleaning! ' } } tasks.

Which DSL does Gradle use?

gradle file which helps Gradle build tool to specify this build is a debug build with certain keyAlias etc. So these building blocks, API, etc are formats to use Gradle in android, hence called Gradle DSL (domain-specific language).

Which Gradle display available tasks?

To see which tasks are available for our build we can run Gradle with the command-line option -t or --tasks. Gradle outputs the available tasks from our build script. By default only the tasks which are dependencies on other tasks are shown. To see all tasks we must add the command-line option --all.


1 Answers

Try this:

subprojects {
    apply<JavaLibraryPlugin>()
    apply<MavenPublishPlugin>()

    group = "de.bentolor.sampleproject"
    version = "0.1.0"

    repositories {
        jcenter()
    }

    dependencies {
        val implementation by configurations
        val testImplementation by configurations

        implementation("commons-logging:commons-logging:1.2")
        testImplementation("junit:junit:4.12")
    }

    // This will work, but as long as these tasks are need only for publishing you can declare them inplace later where you need 
    // tasks {
    //     val sourcesJar by creating(Jar::class) {
    //         val sourceSets: SourceSetContainer by project
    //         from(sourceSets["main"].allJava)
    //         classifier = "sources"
    //     }
    //     val javadoc by getting(Javadoc::class)
    //     val javadocJar by creating(Jar::class) {
    //         from(javadoc)
    //         classifier = "javadoc"
    //     }
    // }

    configure<JavaPluginExtension> {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    configure<PublishingExtension> {
        publications {
            create<MavenPublication>(project.name) {
                from(components["java"])

                // If you configured them before
                // val sourcesJar by tasks.getting(Jar::class)
                // val javadocJar by tasks.getting(Jar::class)

                val sourcesJar by tasks.creating(Jar::class) {
                    val sourceSets: SourceSetContainer by project

                    from(sourceSets["main"].allJava)
                    classifier = "sources"
                }
                val javadocJar by tasks.creating(Jar::class) {
                    from(tasks.get("javadoc"))
                    classifier = "javadoc"
                }

                artifact(sourcesJar)
                artifact(javadocJar)
            }
        }
    }
}

A few notes:

  • Why use String-based apply, when you can do a type-safe apply<T>()?
  • Why use invokes on stings in dependencies, when you can use delegates, which is less hacky and better refactorable.
  • Consider using implementation instead of compile

Why sourceSets is not working in a multi-module project?

When you're using Kotlin DSL it generates accessors for projects based on the applied plugins. It's a two-step process: first Gradle processes plugins (that's why it's recommended to put them in plugins block) and generates accessors and then you can use them in your code (accessors are generated as Kotlin extensions for Project, NamedDomainObjectContainer and so on). But if you're configuring subprojects there are two issues:

  • Parent project evaluates before child, so the extensions for child are not known in parent.
  • The set of plugins applied to parent and child is different and you need to use children accessors in parent.

sourceSets is one of the accessors generated by Kotlin DSL for children. And it's just not available in parent. You can try it yourself: apply only java plugin in subprojects. sourceSets will be available in children build scripts, but not in parent.

This is also why you can use java in children, but have to use configure<JavaPluginExtension> when configuring it in parent.

But you can use delegates to get references for domain objects, like tasks, source sets, configuration and so on.

like image 148
madhead - StandWithUkraine Avatar answered Sep 20 '22 04:09

madhead - StandWithUkraine