gradle / kotlin-dsl-samples

Samples builds using the Gradle Kotlin DSL
https://gradle.org/kotlin/
Other
3.71k stars 434 forks source link

buildSrc/build.gradle.kts cannot resolve references defined in buildSrc/src #1320

Closed ninrod closed 5 years ago

ninrod commented 5 years ago

Expected Behavior

The user should be able to reference objects and functions defined in his buildSrc/src/main/kotlin directory.

Current Behavior

I have a working kotlin dsl gradle build: kotlin-dsl-gradle-experiment

I've written a function in my buildSrc/src/main/kotlin/test.kt file as follows:

package org.ninrod.backend.build

import java.io.File

fun doWeHaveToUseArtifactory(): Boolean {
    val centos = File("/etc/centos-release").exists()
    val workbench = File("/etc/hostname").readText().trim() == "workbench"
    return centos && workbench
}

I'd love to use that function inside my buildSrc/build.gradle.kts instead of duplicating myself in every kotlin dsl block:

buildscript {
    val artifactory = "http://artifactory/artifactory/gradle"
    fun doWeHaveToUseArtifactory(): Boolean {
        val centos = File("/etc/centos-release").exists()
        val workbench = File("/etc/hostname").readText().trim() == "workbench"
        return centos && workbench
    }
    repositories {
        if (doWeHaveToUseArtifactory()) {
            println("configuring artifactory for plugin repos")
            maven {
                url = uri(artifactory)
            }
        } else {
            println("we are using mavencentral for plugins")
            maven {
                mavenCentral()
                jcenter()
            }
        }
    }
    dependencies {
        if (doWeHaveToUseArtifactory()) {
            println("we are going to add the classpath of the org.jfrog.buildinfo plugin")
            classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4.9.0")
        }
        classpath("gradle.plugin.org.gradle.kotlin:gradle-kotlin-dsl-plugins:1.1.1")
    }
}

apply(plugin = "org.gradle.kotlin.kotlin-dsl")

allprojects {
    repositories {
        val artifactory = "http://artifactory/artifactory/gradle"
        fun doWeHaveToUseArtifactory(): Boolean {
            val centos = File("/etc/centos-release").exists()
            val workbench = File("/etc/hostname").readText().trim() == "workbench"
            return centos && workbench
        }
        if (doWeHaveToUseArtifactory()) {
            maven {
                url = uri(artifactory)
            }
        } else {
            mavenCentral()
            jcenter()
        }
    }
}

Context

this affects all kotlin-dsl users that have to configure interesting/complex behaviour and have to resort to logic which should be reusable.

These actions does not work:

Steps to Reproduce (for bugs)

simply clone the kotlin-dsl-gradle-experiment and verify the behaviour:

$ git clone https://github.com/ninrod/kotlin-dsl-gradle-experiment.git
$ cd kotlin-dsl-gradle-experiment
$ gradle build
# verify that the build works
# now try to change the buildSrc/build.gradle.kts to reuse the function define in buildSrc/src/main/kotlin/test.kt

$ gradle build
# verify that nothing works

Your Environment


Gradle 5.1.1

Build time: 2019-01-10 23:05:02 UTC Revision: 3c9abb645fb83932c44e8610642393ad62116807

Kotlin DSL: 1.1.1 Kotlin: 1.3.11 Groovy: 2.5.4 Ant: Apache Ant(TM) version 1.9.13 compiled on July 10 2018 JVM: 1.8.0_192 (Oracle Corporation 25.192-b12) OS: Linux 4.9.87-linuxkit-aufs amd64

JLLeitschuh commented 5 years ago

You can see a potential way to solve this problem here:

I created a script that I could apply from either context and then call a function that that script exports.

https://github.com/gradle/kotlin-dsl/issues/821#issue-314655076

ninrod commented 5 years ago

@JLLeitschuh that's so hacky. So I understand that there is no support yet for code reuse when using the buildSrc strategy, yes?

JLLeitschuh commented 5 years ago

This is the only way I've figured out where I can reuse code in my buildSrc/build.gradle.kts, buildSrc/settings.gradle.kts, build.gradle.kts, & settings.gradle.kts file.

The problem is that you can't use code in your buildSrc project in your buildSrc/build.gradle.kts or your buildSrc/settings.gradle.kts.

I agree, I wish there was a better solution, but I haven't thought of anything better and I've been using this solution for over 2 years now.

Any better solutions would be welcome.

ninrod commented 5 years ago

I understand. I did not quite grasp the workaround though. Do you have a repo somewhere where this strategy is working? for reference purpouses?

ninrod commented 5 years ago

problem I'm having is this:

..e-experiment ➜ make
gradle build

> Configure project :buildSrc
buildSrc: we are using jcenter for plugins
buildSrc: we are using jcenter for plugins

> Configure project :
e: /home/ninrod/code/sources/kotlin-dsl-gradle-experiment/build.gradle.kts:16:13: Unresolved reference: doWeHaveToUseArtifactory

FAILURE: Build failed with an exception.

* Where:
Build file '/home/ninrod/code/sources/kotlin-dsl-gradle-experiment/build.gradle.kts' line: 16

* What went wrong:
Script compilation error:

  Line 16:         if (doWeHaveToUseArtifactory()) {
                       ^ Unresolved reference: doWeHaveToUseArtifactory

1 error

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/5.1.1/userguide/command_line_interface.html#sec:command_line_warnings

BUILD FAILED in 0s
makefile:4: recipe for target 'build' failed
make: *** [build] Error 1
...e-experiment ➜ 

the functions.gradle.kts file is define like this:

import java.io.File

//the hack below was stolen from @JLLeitschuh.
//source: https://github.com/gradle/kotlin-dsl/issues/821#issue-314655076

// BEGIN HACK --------------------------

/**
 * Makes it so that this script can be called with `this` as both a
 * [KotlinBuildScript] & [KotlinSettingsScript] by providing an adapter
 * to the two things that `this` could be.
 */
fun <R> callBasedOnContext(
    ifBuildScript: KotlinBuildScript.() -> R,
    ifSettingsScript: KotlinSettingsScript.() -> R
): R {
    /*
     * A bit of a hack to get around a compiler error when trying to do
     * `is KotlinBuildScript` and `is KotlinSettingsScript`.
     */
    val kotlinProjectClass: KClass<*> = KotlinBuildScript::class
    val kotlinSettingsClass: KClass<*> = KotlinSettingsScript::class

    return when {
        kotlinProjectClass.isInstance(this) -> (this as KotlinBuildScript).ifBuildScript()
        kotlinSettingsClass.isInstance(this) -> (this as KotlinSettingsScript).ifSettingsScript()
        else -> throw AssertionError("$this is not being applied to a supported type.")
    }
}

val extra: ExtraPropertiesExtension by lazy {
    callBasedOnContext(
        ifBuildScript = { extra },
        ifSettingsScript = { (settings as ExtensionAware).extra }
    )
}

fun hasPropertyHelper(propertyName: String): Boolean {
    return callBasedOnContext(
        ifBuildScript = { hasProperty(propertyName) },
        ifSettingsScript = { (settings as ExtensionAware).extra.properties.containsKey(propertyName) }
    )
}

fun propertyHelper(propertyName: String): Any? {
    return callBasedOnContext(
        ifBuildScript = { property(propertyName) },
        ifSettingsScript = { (settings as ExtensionAware).extra.properties[propertyName] }
    )
}

// HACK ENDED -------------------

// my stuff

fun hello(): String = "hello, world!"

fun doWeHaveToUseArtifactory(): Boolean {
    val centos = File("/etc/centos-release").exists()
    val workbench = File("/etc/hostname").readText().trim() == "workbench"
    return centos && workbench
}

// trying to "export" the function. does not work"
extra["doWeHaveToUseArtifactory"] = this::doWeHaveToUseArtifactory

my build.gradle.kts file is importing the functions.gradle.kts file like so:

import org.gradle.jvm.tasks.Jar

apply {
    from("functions.gradle.kts")
}

buildscript {
    // sadly, we have to import our values inside buildscript because this block does not get anything from outside
    val artifactory_gradle: String by project
    val kotlin_version: String by project

    repositories {

        // BUG HERE: I have to use the full qualified name of the doWeHaveToUseArtifactory
        // because the buildScript block does not respect the top level defined imports!
        if (doWeHaveToUseArtifactory()) {
            println("configuring artifactory for plugin repos")
            maven {
                url = uri(artifactory_gradle)
            }
        } else {
            println("we are using jcenter for plugins")
            maven {
                url = uri("https://plugins.gradle.org/m2/")
            }
            jcenter()
        }
    }
    dependencies {
        // BUG HERE: I have to use the full qualified name of the doWeHaveToUseArtifactory
        // because the buildScript block does not respect the top level defined imports!
        if (org.ninrod.backend.build.doWeHaveToUseArtifactory()) {
            println("we are going to add the classpath of the org.jfrog.buildinfo plugin")
            classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4.9.0")
        }
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
    }
}

if (doWeHaveToUseArtifactory()) {
    apply(plugin = "com.jfrog.artifactory")
}

apply(plugin = "kotlin")
plugins {
    application
}

application {
    mainClassName = "org.ninrod.backend.EntrypointKt"
    version = "0.0.1"
}

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

repositories {
    val artifactory = "http://artifactory/artifactory/gradle"
    if (doWeHaveToUseArtifactory()) {
        maven {
            url = uri(artifactory)
        }
    } else {
        println("we are using jcenter for plugins")
        maven {
            url = uri("https://plugins.gradle.org/m2/")
        }
        jcenter()
    }
}

dependencies {

    // this block fetches properties from gradle.properties
    val kotlin_version: String by project
    val exposed_version: String by project
    val junit5_version: String by project
    val postgresql_driver_version: String by project

    // kotlin
    compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
    compile("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")

    // db
    compile("org.jetbrains.exposed:exposed:$exposed_version")
    compile("org.jetbrains.exposed:spring-transaction:$exposed_version")
    compile("org.postgresql:postgresql:$postgresql_driver_version")

    // tests
    testCompile("org.junit.jupiter:junit-jupiter-api:$junit5_version")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5_version")
}

tasks {
    "jar"(Jar::class) {
        baseName = project.name
        manifest {
            attributes["Main-Class"] = application.mainClassName
        }
        from( configurations.runtime.get().map { if (it.isDirectory) it else zipTree(it) })
    }

    val dumpTest by creating {
        println("CONFIGURATION PHASE!!!")
        println(hello())
        println("temos que usar artifactory? " + doWeHaveToUseArtifactory())
        doLast {
            println("EXECUTION PHASE!!!")
        }
    }
}
eskatos commented 5 years ago

Gradle is working as intended wrt the original issue description:

buildSrc/build.gradle.kts cannot resolve references defined in buildSrc/src

This is a chicken and egg problem. buildSrc/build.gradle.kts contains the build logic that describes how to build the code from buildSrc/src, so it cannot depend on what gets built.

Trying to work around that using some dynamic construct won't make things easy.

Now, if you want to share code type-safely between your "buildSrc build logic"' and "buildSrc sources" you need to extract that code into something new. That new thing is another build, either via a published artifact or a Gradle included build. The recent webinar on the Kotlin DSL will provide some answers, also see Integrating Separate Gradle build in the user guide.

Please use the Gradle forums for support questions https://discuss.gradle.org/

bejibx commented 5 years ago

Now, if you want to share code type-safely between your "buildSrc build logic"' and "buildSrc sources" you need to extract that code into something new. That new thing is another build, either via a published artifact or a Gradle included build. The recent webinar on the Kotlin DSL will provide some answers, also see Integrating Separate Gradle build in the user guide.

I tried it but sadly got error message “Cannot include build ‘dependenciesDigest’ in build ‘buildSrc’. This is not supported yet.”. I’m using Gradle version 5.4.1