Pieter VDE
Pieter VDE

Reputation: 2235

Extract common methods from Gradle build script

I have a Gradle build script (build.gradle), in which I created some tasks. These tasks consist mostly of method calls. The called methods are also in the build script.

Now, here's the situation:

I am creating a fair amount of build scripts, which contain different tasks, but utilise the same methods from the original script. Thus, I would like to extract these "common methods" in some way, so I can easily re-use them instead of copying them for each new script I create.

If Gradle were PHP, something like the following would be ideal:

//script content
...
require("common-methods.gradle");
...
//more script content

But of course, that isn't possible. Or is it?

Anyway, how can I achieve this result? What is the best possible method to do this? I've read through the Gradle documentation already, but I can't seem to determine which method will be the easiest and best suited for this.


UPDATE:

I've managed to extract the methods in another file

(using apply from: 'common-methods.gradle'),

so the structure is as follows:

parent/
      /build.gradle              // The original build script
      /common-methods.gradle     // The extracted methods
      /gradle.properties         // Properties used by the build script

After executing a task from build.gradle, I've bumped into a new problem: apparently, methods don't get recognized when they're in common-methods.gradle.

How can that be fixed?

Upvotes: 108

Views: 40181

Answers (6)

LeoFuso
LeoFuso

Reputation: 72

You can create a plugin by using the conventions pattern.

By doing so, you can create a plugin that shares some functions. Here's an example:

Versions.kt

object Versions {

    inline fun Configuration.replace(replacement: Provider<MinimalExternalModuleDependency>, crossinline cause: () -> String? = { null }) {
        val dependency = replacement.get()
        resolutionStrategy.eachDependency {
            if(requested.group == dependency.group && requested.name == dependency.name) {
                useVersion(dependency.version!!)
                val because = cause.invoke()
                if (because != null) {
                    because(because)
                }
            }
        }
    }
}

And its usage: build.gradle.kts


import Versions.replace


configurations.all {
    replace(libs.jackson.core) { "Conflict resolution." }
    replace(libs.jackson.databind) { "The version of the compiler has a security issue associated with this dependency." }
    replace(libs.apache.commons.text) { "The version of the compiler has a security issue associated with this dependency." }
    replace(libs.apache.commons.lang) { "Conflict resolution." }
    replace(libs.slf4j.api) { "Conflict resolution." }
}

Upvotes: 1

GarouDan
GarouDan

Reputation: 3833

Another approach for Kotlin DSL could be:

my-plugin.gradle.kts

extra["sum"] = { x: Int, y: Int -> x + y }

settings.gradle.kts

@Suppress("unchecked_cast", "nothing_to_inline")
inline fun <T> uncheckedCast(target: Any?): T = target as T
    
apply("my-plugin.gradle.kts")
    
val sum = uncheckedCast<(Int, Int) -> Int>(extra["sum"])
    
println(sum(1, 2))

Upvotes: 2

GitProphet
GitProphet

Reputation: 1038

Using the Kotlin DSL it works like this:

build.gradle.kts:

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

val foo = extra["foo"] as () -> Unit
foo()

external.gradle.kts:

extra["foo"] = fun() {
  println("Hello world!")
}

Upvotes: 13

Chilly_Vanilly
Chilly_Vanilly

Reputation: 75

I would suggest a slight adjustment to Matthias Braun's answer, in that instead of writing the same method-name twice and still have it clear and consise, why not simply do the following:

ext.commonMethod1 = (param) -> {
    return true
} as Closure<boolean>

The usage of the as-operator simply tells one explicitly, that this function will return a value of boolean-type.

Because after all, this still is Groovy goodness. Neat huh?

Upvotes: 3

Matthias Braun
Matthias Braun

Reputation: 34353

Building on Peter's answer, this is how I export my methods:

Content of helpers/common-methods.gradle:

// Define methods as usual
def commonMethod1(param) {
    return true
}
def commonMethod2(param) {
    return true
}

// Export methods by turning them into closures
ext {
    commonMethod1 = this.&commonMethod1
    otherNameForMethod2 = this.&commonMethod2
}

And this is how I use those methods in another script:

// Use double-quotes, otherwise $ won't work
apply from: "$rootDir/helpers/common-methods.gradle"

// You can also use URLs
//apply from: "https://bitbucket.org/mb/build_scripts/raw/master/common-methods.gradle"

task myBuildTask {
    def myVar = commonMethod1("parameter1")
    otherNameForMethod2(myVar)
}

Here's more on converting methods to closures in Groovy.

Upvotes: 196

Peter Niederwieser
Peter Niederwieser

Reputation: 123910

It isn't possible to share methods, but you can share extra properties containing a closure, which boils down to the same thing. For example, declare ext.foo = { ... } in common-methods.gradle, use apply from: to apply the script, and then call the closure with foo().

Upvotes: 81

Related Questions