1

What is the recommended way to extract gradle build code blocks to external scripts? Notice these scripts should support references to the gradle project, extra etc. So - compiling kt files in buildSrc isn't what I'm looking for.

I've tried to create files like logger.gradle.kts and these seem to "know" the project ref / compile, but any fun I write in them is not referenceable in the main build file although I apply like this:

`apply (from = "logger.gradle.kts")`

The error I get as part of the build is:

Unresolved reference: logInfo - where logInfo is a fun in logger.gradle.kts.

This is the logger file I am using:

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.internal.logging.text.StyledTextOutput
import org.gradle.internal.logging.text.StyledTextOutputFactory
import org.gradle.internal.logging.services.DefaultStyledTextOutputFactory
import org.gradle.internal.logging.text.StyledTextOutput.Style


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 = KotlinBuildScript::class
    val kotlinSettingsClass = 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] }
    )
}

extra["logDebug"] = this::logDebug
extra["logInfo"] = this::logInfo
extra["logWarn"] = this::logWarn
extra["logError"] = this::logError
extra["logTitle"] = this::logTitle
extra["logStyles"] = this::logStyles

val loggerOut = DefaultStyledTextOutputFactory(null, null).create("styled_output")
val loggerOutError = loggerOut.withStyle(Style.Failure)
val loggerOutWarn = loggerOut.withStyle(Style.Description)
val loggerOutInfo = loggerOut.withStyle(Style.Success)
val loggerOutDebug = loggerOut.withStyle(Style.Normal)
val loggerOutTitle = loggerOut.withStyle(Style.Header)

fun log(message: String, out: StyledTextOutput) {
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        project.logger.quiet(message)
    } else {
        out.println(message)
    }
}

fun logTitle(message: String) {
    log("\n---------------------------------------------", loggerOutTitle)
    log("  ${message.toUpperCase()}", loggerOutTitle)
    log("---------------------------------------------", loggerOutTitle)
}

fun logDebug(message: String) {
    log("[DEBUG] " + message, loggerOutDebug)
}

fun logInfo(message: String) {
    log("[INFO] " + message, loggerOutInfo)
}

fun logWarn(message: String) {
    log("[WARN] " + message, loggerOutWarn)
}

fun logError(message: String) {
    log("[ERROR] " + message, loggerOutError)
}

fun logStyles() {
    val out = DefaultStyledTextOutputFactory(null, null).create("styled_test")

    log("Style: Normal", out.withStyle(Style.Normal))
    log("Style: Header", out.withStyle(Style.Header))
    log("Style: UserInput", out.withStyle(Style.UserInput))
    log("Style: Identifier", out.withStyle(Style.Identifier))
    log("Style: Description", out.withStyle(Style.Description))
    log("Style: ProgressStatus", out.withStyle(Style.ProgressStatus))
    log("Style: Success", out.withStyle(Style.Success))
    log("Style: SuccessHeader", out.withStyle(Style.SuccessHeader))
    log("Style: Failure", out.withStyle(Style.Failure))
    log("Style: FailureHeader", out.withStyle(Style.FailureHeader))
    log("Style: Info", out.withStyle(Style.Info))
    log("Style: Error", out.withStyle(Style.Error))
}

And usages in build.gradle.kts:

plugins {
    base
    idea
    `java-library`
    scala
    apply(from = "gradle_scripts/logger.gradle.kts")
}

And invoking like this:

logInfo("Application Version: ${version}")

the method fails on:

Unresolved reference: logInfo

Note: If I add the functions to extra (which is recommendation I saw somewhere on hos to expose methods between scripts files):

extra["logDebug"] = this::logDebug
extra["logInfo"] = this::logInfo
extra["logWarn"] = this::logWarn
extra["logError"] = this::logError
extra["logTitle"] = this::logTitle
extra["logStyles"] = this::logStyles

It then fails on:

Logger_gradle@a1f3cf9 is not being applied to a supported type.
aSemy
  • 5,485
  • 2
  • 25
  • 51
YaOg
  • 1,748
  • 5
  • 24
  • 43

1 Answers1

1

Gradle Kotlin DSL does not support this operation if I'm not wrong.

If you try to add a "redundant" public modifier to logInfo(...):

public fun logInfo(s: String) {
    println(s)
}

You will get a compilation error:

Modifier 'public' is not applicable to 'local function'

The recommended way is leveraging Extension Functions, for example:

val Project.isSnapshotBuild
    get() = (version as String).contains("snapshot", true)

check a complete sample here.

2BAB
  • 1,215
  • 10
  • 18
  • Thanks, are you sure that Extension Functions work also for the root project? I am not using modules here. I want to extract build logic from the root project. – YaOg Jan 02 '22 at 05:13
  • fun Project.logInfo(message: String) { log("[INFO] " + message, loggerOutInfo) } still causes: Unresolved reference: logInfo – YaOg Jan 02 '22 at 05:27
  • @YaOg You need to put the extension function to `*.kt`, not `.gradle.kts`. I have double-checked from my local project, it works for both module/root `build.gradle.kts`. – 2BAB Jan 02 '22 at 05:39
  • I tried that as well. For some reason buildSrc task skips compileKotlin. I only see compile java and groovy. However, my original question was to use kts files - i.e. no need to pre-compile. Is that not supported? It works fine in gradle groovy (using apply from ...). – YaOg Jan 02 '22 at 09:11
  • @YaOg Yes, the standalone script plugin written by Gradle Kotlin DSL has many limitations, from what I tested, it does not support the Extension Function trick. Precompiled script plugin is better in `*.gradle.kts` world. – 2BAB Jan 02 '22 at 11:40
  • "For some reason buildSrc task skips compileKotlin" -> did you apply the ``kotlin-dsl`` plugin to buildSrc module? Or you may want to create a separate question. – 2BAB Jan 02 '22 at 11:42
  • This seems to do the trick: https://stackoverflow.com/a/52139585/200937 – YaOg Jan 02 '22 at 12:14
  • Yep, it does, but it's a bit verbose, you have to obtain and redeclare any methods you are going to use again in each script. In a single project with a few modules only, buildSrc + precompiled *.gradle.kts / *.kt works well. Among multi projects, a binary convention plugin + includeBuild also can be a better choice. – 2BAB Jan 02 '22 at 12:43
  • of all the myriad of answers this one was the only that makes sense to me. key point is to put extension functions in *.kt. thank you. – ror Jul 15 '22 at 09:43